From 1eed928bc7df41033a101dbd4dbfcbdd13912ab8 Mon Sep 17 00:00:00 2001 From: Edward Rudd Date: Fri, 18 Feb 2022 14:26:32 -0500 Subject: [PATCH 1/5] update chrono-tz to 0.6.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 05c044f7..0bb07f13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ fast_chemail = "0.9.6" # Feature optional dependencies bson = { version = "2.0.0", optional = true, features = ["chrono-0_4"] } chrono = { version = "0.4.19", optional = true } -chrono-tz = { version = "0.5.3", optional = true } +chrono-tz = { version = "0.6.1", optional = true } hashbrown = { version = "0.12.0", optional = true } iso8601-duration = { version = "0.1.0", optional = true } log = { version = "0.4.14", optional = true } From 9cca3087947945701b991d90cb5d51aa20bc15e6 Mon Sep 17 00:00:00 2001 From: Dylan DPC <99973273+Dylan-DPC@users.noreply.github.com> Date: Thu, 10 Mar 2022 13:24:07 +0100 Subject: [PATCH 2/5] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8177c0ef..a97b4d49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ futures-util = { version = "0.3.0", default-features = false, features = ["io", indexmap = "1.6.2" once_cell = "1.7.2" pin-project-lite = "0.2.6" -regex = "1.4.5" +regex = "1.5.5" serde = { version = "1.0.125", features = ["derive"] } serde_json = "1.0.64" thiserror = "1.0.24" From 5e95c817cb80d2849f4d7cadf8187efb16c44dc7 Mon Sep 17 00:00:00 2001 From: Jarrett Tierney Date: Tue, 22 Mar 2022 20:43:54 -0700 Subject: [PATCH 3/5] Add feature(bson-uuid) which will enable Uuid's from the bson crate --- Cargo.toml | 2 +- src/types/external/bson.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 67987dd4..2ad924f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "3.0.36" +version = "3.0.37" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "A GraphQL server library implemented in Rust" diff --git a/src/types/external/bson.rs b/src/types/external/bson.rs index 2686d049..b48f931d 100644 --- a/src/types/external/bson.rs +++ b/src/types/external/bson.rs @@ -2,6 +2,8 @@ use bson::{oid::ObjectId, Bson, Document}; #[cfg(feature = "chrono")] use bson::DateTime as UtcDateTime; +#[cfg(feature = "bson-uuid")] +use bson::Uuid; #[cfg(feature = "chrono")] use chrono::{DateTime, Utc}; @@ -21,6 +23,21 @@ impl ScalarType for ObjectId { } } +#[cfg(feature = "bson-uuid")] +#[Scalar(internal, name = "UUID")] +impl ScalarType for Uuid { + fn parse(value: Value) -> InputValueResult { + match value { + Value::String(s) => Ok(Uuid::parse_str(&s)?), + _ => Err(InputValueError::expected_type(value)), + } + } + + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } +} + #[cfg(feature = "chrono")] #[Scalar(internal, name = "DateTime")] impl ScalarType for UtcDateTime { From 1c172cc4f9c6874dd0f594f9ce462c076c99ea39 Mon Sep 17 00:00:00 2001 From: Paul Nguyen Date: Mon, 18 Apr 2022 18:53:55 +0200 Subject: [PATCH 4/5] async-graphql-axum: update tokio-util --- integrations/axum/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index e34b368c..b32ef45c 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -20,6 +20,6 @@ bytes = "1.0.1" http-body = "0.4.2" serde_json = "1.0.66" serde_urlencoded = "0.7.0" -tokio-util = { version = "0.6.7", features = ["io", "compat"] } +tokio-util = { version = "0.7.1", features = ["io", "compat"] } futures-util = "0.3.0" tower-service = "0.3" From 0ebcccd8d90d6cd99b8f40583904dfadb55012fb Mon Sep 17 00:00:00 2001 From: Paul Nguyen Date: Wed, 13 Apr 2022 05:00:24 +0200 Subject: [PATCH 5/5] Allow introspection only schemas --- src/context.rs | 4 +- src/registry/mod.rs | 9 ++-- src/request.rs | 29 +++++++++++ src/schema.rs | 56 ++++++++++++++++++--- src/types/query_root.rs | 24 +++++++-- tests/introspection.rs | 106 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 15 deletions(-) diff --git a/src/context.rs b/src/context.rs index 39970b15..c1bfc1dd 100644 --- a/src/context.rs +++ b/src/context.rs @@ -13,11 +13,11 @@ use http::HeaderValue; use serde::ser::{SerializeSeq, Serializer}; use serde::Serialize; -use crate::extensions::Extensions; use crate::parser::types::{ Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet, }; use crate::schema::SchemaEnv; +use crate::{extensions::Extensions, schema::IntrospectionMode}; use crate::{ Error, InputType, Lookahead, Name, OneofObjectType, PathSegment, Pos, Positioned, Result, ServerError, ServerResult, UploadValue, Value, @@ -244,7 +244,7 @@ pub struct QueryEnvInner { pub session_data: Arc, pub ctx_data: Arc, pub http_headers: Mutex, - pub disable_introspection: bool, + pub introspection_mode: IntrospectionMode, pub errors: Mutex>, } diff --git a/src/registry/mod.rs b/src/registry/mod.rs index b50811fc..c65d7183 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -9,13 +9,14 @@ use indexmap::map::IndexMap; use indexmap::set::IndexSet; pub use crate::model::__DirectiveLocation; -use crate::parser::types::{ - BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition, -}; use crate::{ model, Any, Context, InputType, OutputType, Positioned, ServerResult, SubscriptionType, Value, VisitorContext, }; +use crate::{ + parser::types::{BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition}, + schema::IntrospectionMode, +}; pub use cache_control::CacheControl; @@ -401,7 +402,7 @@ pub struct Registry { pub query_type: String, pub mutation_type: Option, pub subscription_type: Option, - pub disable_introspection: bool, + pub introspection_mode: IntrospectionMode, pub enable_federation: bool, pub federation_subscription: bool, } diff --git a/src/request.rs b/src/request.rs index 7e24d82f..4b04afba 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::parser::parse_query; use crate::parser::types::ExecutableDocument; +use crate::schema::IntrospectionMode; use crate::{Data, ParseRequestError, ServerError, UploadValue, Value, Variables}; /// GraphQL request. @@ -14,6 +15,7 @@ use crate::{Data, ParseRequestError, ServerError, UploadValue, Value, Variables} /// variables. The names are all in `camelCase` (e.g. `operationName`). #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[non_exhaustive] pub struct Request { /// The query source of the request. #[serde(default)] @@ -42,11 +44,17 @@ pub struct Request { pub extensions: HashMap, /// Disable introspection queries for this request. + /// This option has priority over `introspection_mode` when set to true. + /// `introspection_mode` has priority when `disable_introspection` set to `false`. #[serde(skip)] pub disable_introspection: bool, #[serde(skip)] pub(crate) parsed_query: Option, + + /// Sets the introspection mode for this request (defaults to [IntrospectionMode::Enabled]). + #[serde(skip)] + pub introspection_mode: IntrospectionMode, } impl Request { @@ -61,6 +69,7 @@ impl Request { extensions: Default::default(), disable_introspection: false, parsed_query: None, + introspection_mode: IntrospectionMode::Enabled, } } @@ -90,6 +99,15 @@ impl Request { #[must_use] pub fn disable_introspection(mut self) -> Self { self.disable_introspection = true; + self.introspection_mode = IntrospectionMode::Disabled; + self + } + + /// Only allow introspection queries for this request. + #[must_use] + pub fn only_introspection(mut self) -> Self { + self.disable_introspection = false; + self.introspection_mode = IntrospectionMode::IntrospectionOnly; self } @@ -232,6 +250,17 @@ impl BatchRequest { pub fn disable_introspection(mut self) -> Self { for request in self.iter_mut() { request.disable_introspection = true; + request.introspection_mode = IntrospectionMode::Disabled; + } + self + } + + /// Only allow introspection queries for each request. + #[must_use] + pub fn introspection_only(mut self) -> Self { + for request in self.iter_mut() { + request.disable_introspection = false; + request.introspection_mode = IntrospectionMode::IntrospectionOnly; } self } diff --git a/src/schema.rs b/src/schema.rs index 9480cfd9..93fb7fe4 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use futures_util::stream::{self, Stream, StreamExt}; use indexmap::map::IndexMap; -use crate::context::{Data, QueryEnvInner}; use crate::custom_directive::CustomDirectiveFactory; use crate::extensions::{ExtensionFactory, Extensions}; use crate::model::__DirectiveLocation; @@ -17,11 +16,29 @@ use crate::resolver_utils::{resolve_container, resolve_container_serial}; use crate::subscription::collect_subscription_streams; use crate::types::QueryRoot; use crate::validation::{check_rules, ValidationMode}; +use crate::{ + context::{Data, QueryEnvInner}, + EmptyMutation, EmptySubscription, +}; use crate::{ BatchRequest, BatchResponse, CacheControl, ContextBase, InputType, ObjectType, OutputType, QueryEnv, Request, Response, ServerError, SubscriptionType, Variables, ID, }; +/// Introspection mode +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntrospectionMode { + IntrospectionOnly, + Enabled, + Disabled, +} + +impl Default for IntrospectionMode { + fn default() -> Self { + IntrospectionMode::Enabled + } +} + /// Schema builder pub struct SchemaBuilder { validation_mode: ValidationMode, @@ -58,7 +75,14 @@ impl SchemaBuilder /// Disable introspection queries. #[must_use] pub fn disable_introspection(mut self) -> Self { - self.registry.disable_introspection = true; + self.registry.introspection_mode = IntrospectionMode::Disabled; + self + } + + /// Only process introspection queries, everything else is processed as an error. + #[must_use] + pub fn introspection_only(mut self) -> Self { + self.registry.introspection_mode = IntrospectionMode::IntrospectionOnly; self } @@ -303,7 +327,7 @@ where } else { Some(Subscription::type_name().to_string()) }, - disable_introspection: false, + introspection_mode: IntrospectionMode::Enabled, enable_federation: false, federation_subscription: false, }; @@ -515,7 +539,11 @@ where session_data, ctx_data: query_data, http_headers: Default::default(), - disable_introspection: request.disable_introspection, + introspection_mode: if request.disable_introspection { + IntrospectionMode::Disabled + } else { + request.introspection_mode + }, errors: Default::default(), }; Ok((QueryEnv::new(env), validation_result.cache_control)) @@ -532,7 +560,15 @@ where let res = match &env.operation.node.ty { OperationType::Query => resolve_container(&ctx, &self.query).await, - OperationType::Mutation => resolve_container_serial(&ctx, &self.mutation).await, + OperationType::Mutation => { + if self.env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly + || env.introspection_mode == IntrospectionMode::IntrospectionOnly + { + resolve_container_serial(&ctx, &EmptyMutation).await + } else { + resolve_container_serial(&ctx, &self.mutation).await + } + } OperationType::Subscription => Err(ServerError::new( "Subscriptions are not supported on this transport.", None, @@ -627,7 +663,15 @@ where ); let mut streams = Vec::new(); - if let Err(err) = collect_subscription_streams(&ctx, &schema.subscription, &mut streams) { + let collect_result = if schema.env.registry.introspection_mode + == IntrospectionMode::IntrospectionOnly + || env.introspection_mode == IntrospectionMode::IntrospectionOnly + { + collect_subscription_streams(&ctx, &EmptySubscription, &mut streams) + } else { + collect_subscription_streams(&ctx, &schema.subscription, &mut streams) + }; + if let Err(err) = collect_result { yield Response::from_errors(vec![err]); } diff --git a/src/types/query_root.rs b/src/types/query_root.rs index d984b965..f8671144 100644 --- a/src/types/query_root.rs +++ b/src/types/query_root.rs @@ -2,9 +2,12 @@ use std::borrow::Cow; use indexmap::map::IndexMap; -use crate::model::{__Schema, __Type}; use crate::parser::types::Field; use crate::resolver_utils::{resolve_container, ContainerType}; +use crate::{ + model::{__Schema, __Type}, + schema::IntrospectionMode, +}; use crate::{ registry, Any, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError, ServerResult, SimpleObject, Value, @@ -24,7 +27,13 @@ pub(crate) struct QueryRoot { #[async_trait::async_trait] impl ContainerType for QueryRoot { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection { + if matches!( + ctx.schema_env.registry.introspection_mode, + IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly + ) && matches!( + ctx.query_env.introspection_mode, + IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly, + ) { if ctx.item.node.name.node == "__schema" { let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); let visible_types = ctx.schema_env.registry.find_visible_types(ctx); @@ -54,6 +63,12 @@ impl ContainerType for QueryRoot { } } + if ctx.schema_env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly + || ctx.query_env.introspection_mode == IntrospectionMode::IntrospectionOnly + { + return Ok(None); + } + if ctx.schema_env.registry.enable_federation || ctx.schema_env.registry.has_entities() { if ctx.item.node.name.node == "_entities" { let (_, representations) = ctx.param_value::>("representations", None)?; @@ -93,7 +108,10 @@ impl OutputType for QueryRoot { fn create_type_info(registry: &mut registry::Registry) -> String { let root = T::create_type_info(registry); - if !registry.disable_introspection { + if matches!( + registry.introspection_mode, + IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly + ) { let schema_type = __Schema::create_type_info(registry); if let Some(registry::MetaType::Object { fields, .. }) = registry.types.get_mut(T::type_name().as_ref()) diff --git a/tests/introspection.rs b/tests/introspection.rs index 22832785..6eb2d90f 100644 --- a/tests/introspection.rs +++ b/tests/introspection.rs @@ -1272,3 +1272,109 @@ pub async fn test_disable_introspection() { value!({ "__type": null }) ); } + +#[tokio::test] +pub async fn test_introspection_only() { + let schema = Schema::build(Query, Mutation, EmptySubscription) + .introspection_only() + .finish(); + + // Test whether introspection works. + let query = r#" + { + __type(name: "Mutation") { + name + kind + description + fields { + description + name + type { kind name } + args { name } + } + } + } + "#; + let res_json = value!({ + "__type": { + "name": "Mutation", + "kind": "OBJECT", + "description": "Global mutation", + "fields": [ + { + "description": "simple_mutation description\nline2\nline3", + "name": "simpleMutation", + "type": { + "kind": "NON_NULL", + "name": null + }, + "args": [ + { + "name": "input" + } + ] + } + ] + } + }); + let res = schema.execute(query).await.into_result().unwrap().data; + assert_eq!(res, res_json); + + // Test whether introspection works. + let query = r#" + { + __type(name: "Query") { + name + kind + description + fields { + description + name + type { kind name } + args { name } + } + } + } + "#; + let res_json = value!({ + "__type": { + "name": "Query", + "kind": "OBJECT", + "description": "Global query", + "fields": [ + { + "description": "Get a simple object", + "name": "simpleObject", + "type": { "kind": "NON_NULL", "name": null }, + "args": [] + } + ] + } + }); + let res = schema.execute(query).await.into_result().unwrap().data; + assert_eq!(res, res_json); + + // Queries shouldn't work in introspection only mode. + let query = r#" + { + simpleObject { + a + } + } + "#; + let res_json = value!({ "simpleObject": null }); + let res = schema.execute(query).await.into_result().unwrap().data; + assert_eq!(res, res_json); + + // Mutations shouldn't work in introspection only mode. + let query = r#" + mutation { + simpleMutation(input: { a: "" }) { + a + } + } + "#; + let res_json = value!({ "simpleMutation": null }); + let res = schema.execute(query).await.into_result().unwrap().data; + assert_eq!(res, res_json); +}