Skip to content

Commit

Permalink
disable intro
Browse files Browse the repository at this point in the history
wip

fix

fixes

disable
  • Loading branch information
dotansimha committed Dec 18, 2023
1 parent dc8f0d3 commit 2ac208f
Show file tree
Hide file tree
Showing 10 changed files with 739 additions and 91 deletions.
66 changes: 66 additions & 0 deletions libs/common/src/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,72 @@ impl ParsedGraphQLRequest {
})
}

pub fn executable_operation(&self) -> Option<&Definition<'static, String>> {
match &self.request.operation_name {
Some(op_name) => self.parsed_operation.definitions.iter().find(|v| {
if let Definition::Operation(op) = v {
let name: &Option<String> = match op {
OperationDefinition::SelectionSet(_) => &None,
OperationDefinition::Query(query) => &query.name,
OperationDefinition::Mutation(mutation) => &mutation.name,
OperationDefinition::Subscription(subscription) => &subscription.name,
};

if let Some(actual_name) = name {
return actual_name == op_name;
}
}

false
}),
_ => self.parsed_operation.definitions.iter().find(|v| {
if let Definition::Operation(_) = v {
return true;
}

false
}),
}
}

pub fn is_introspection_query(&self) -> bool {
let operation_to_execute = self.executable_operation();
let root_level_selections = match operation_to_execute {
Some(Definition::Operation(OperationDefinition::SelectionSet(s))) => Some(s),
Some(Definition::Operation(OperationDefinition::Query(q))) => Some(&q.selection_set),
_ => None,
};

if let Some(selections) = root_level_selections {
let all_typename = selections.items.iter().all(|v| {
// TODO: Should we handle Fragments here as well?
if let graphql_parser::query::Selection::Field(field) = v {
return field.name == "__typename";
}

false
});

if all_typename {
return true;
}

let has_some_introspection_fields = selections.items.iter().any(|v| {
if let graphql_parser::query::Selection::Field(field) = v {
return field.name == "__schema" || field.name == "__type";
}

false
});

if has_some_introspection_fields {
return true;
}
}

false
}

#[tracing::instrument(level = "trace", name = "ParsedGraphQLRequest::is_running_mutation")]
pub fn is_running_mutation(&self) -> bool {
if let Some(operation_name) = &self.request.operation_name {
Expand Down
202 changes: 138 additions & 64 deletions libs/config/conductor.schema.json

Large diffs are not rendered by default.

24 changes: 1 addition & 23 deletions libs/config/src/generate-json-schema.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
use conductor_config::ConductorConfig;
use schemars::{
gen::SchemaSettings,
schema::SchemaObject,
visit::{visit_schema_object, Visitor},
};

#[derive(Debug, Clone)]
pub struct PatchDescriptionVisitor;

impl Visitor for PatchDescriptionVisitor {
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
if let Some(desc) = schema
.metadata
.as_mut()
.and_then(|m| m.description.as_mut())
{
*desc = desc.replace("\n\n", "\n");
}

visit_schema_object(self, schema);
}
}
use schemars::gen::SchemaSettings;

pub fn main() {
println!("⚙️ Generating JSON schema for Conductor config file...");
// Please keep this 2019/09, see https://github.com/GREsau/schemars/issues/42#issuecomment-642603632
// Website documentation generator depends on this.
let schema = SchemaSettings::draft2019_09()
.with_visitor(PatchDescriptionVisitor {})
.into_generator()
.into_root_schema_for::<ConductorConfig>();
let as_string = serde_json::to_string_pretty(&schema).unwrap();
Expand Down
16 changes: 14 additions & 2 deletions libs/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pub mod serde_utils;

use interpolate::interpolate;
use plugins::{
CorsPluginConfig, GraphiQLPluginConfig, HttpGetPluginConfig, PersistedOperationsPluginConfig,
PersistedOperationsProtocolConfig, VrlPluginConfig,
CorsPluginConfig, DisableIntrospectionPluginConfig, GraphiQLPluginConfig, HttpGetPluginConfig,
PersistedOperationsPluginConfig, PersistedOperationsProtocolConfig, VrlPluginConfig,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -231,6 +231,18 @@ pub enum PluginDefinition {
config: Option<CorsPluginConfig>,
},

#[serde(rename = "disable_introspection")]
/// Configuration for the Disable Introspection plugin.
DisableItrospectionPlugin {
#[serde(
default = "default_plugin_enabled",
skip_serializing_if = "Option::is_none"
)]
enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
config: Option<DisableIntrospectionPluginConfig>,
},

#[serde(rename = "http_get")]
HttpGetPlugin {
#[serde(
Expand Down
66 changes: 66 additions & 0 deletions libs/config/src/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,63 @@ use crate::{
PluginDefinition,
};

#[derive(Default, Deserialize, Serialize, Debug, Clone, JsonSchema)]
#[schemars(example = "disable_introspection_example1")]
#[schemars(example = "disable_introspection_example2")]
/// The `disable_introspection` plugin allows you to disable introspection for your GraphQL API.
///
/// A [GraphQL introspection query](https://graphql.org/learn/introspection/) is a special GraphQL query that returns information about the GraphQL schema of your API.
///
/// It it [recommended to disable introspection for production environments](https://escape.tech/blog/should-i-disable-introspection-in-graphql/), unless you have a specific use-case for it.
///
/// It can either disable introspection for all requests, or only for requests that match a specific condition (using VRL scripting language).
///
pub struct DisableIntrospectionPluginConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
/// A VRL condition that determines whether to disable introspection for the request. This condition is evaluated only if the incoming GraphQL request is detected as an introspection query.
///
/// The condition is evaluated in the context of the incoming request and have access to the metadata field `%downstream_http_req` (fields: `body`, `uri`, `query_string`, `method`, `headers`).
///
/// The condition must return a boolean value: return `true` to continue and disable the introspection, and `false` to allow the introspection to run.
///
/// In case of a runtime error, or an unexpected return value, the script will be ignored and introspection will be disabled for the incoming request.
pub condition: Option<VrlConfigReference>,
}

fn disable_introspection_example1() -> JsonSchemaExample<PluginDefinition> {
JsonSchemaExample {
metadata: JsonSchemaExampleMetadata::new(
"Disable Introspection",
Some(
"This example disables introspection for all requests for the configured Endpoint.",
),
),
example: PluginDefinition::DisableItrospectionPlugin {
enabled: Some(true),
config: Some(DisableIntrospectionPluginConfig {
..Default::default()
}),
},
}
}

fn disable_introspection_example2() -> JsonSchemaExample<PluginDefinition> {
JsonSchemaExample {
metadata: JsonSchemaExampleMetadata::new(
"Conditional",
Some(
"This example disables introspection for all requests that doesn't have the \"bypass-introspection\" HTTP header.",
),
),
example: PluginDefinition::DisableItrospectionPlugin {
enabled: Some(true),
config: Some(DisableIntrospectionPluginConfig {
condition: Some(VrlConfigReference::Inline { content: "%downstream_http_req.headers.\"bypass-introspection\" != \"1\"".to_string() }),
}),
},
}
}

/// The `cors` plugin enables [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) configuration for your GraphQL API.
///
/// By using this plugin, you can define rules for allowing cross-origin requests to your GraphQL server. This is essential for web applications that need to interact with your API from different domains.
Expand Down Expand Up @@ -776,3 +833,12 @@ pub enum VrlConfigReference {
/// File reference to a VRL file. The file is loaded and executed as a VRL plugin.
File { path: LocalFileReference },
}

impl VrlConfigReference {
pub fn contents(&self) -> &String {
match self {
VrlConfigReference::Inline { content } => content,
VrlConfigReference::File { path } => &path.contents,
}
}
}
Loading

0 comments on commit 2ac208f

Please sign in to comment.