Skip to content

Commit d17ca21

Browse files
[6.x] GraphQL introspection config (#13880)
Co-authored-by: Jason Varga <jason@pixelfear.com>
1 parent 117984c commit d17ca21

7 files changed

Lines changed: 111 additions & 28 deletions

File tree

config/graphql.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,17 @@
8888
'expiry' => 60,
8989
],
9090

91+
/*
92+
|--------------------------------------------------------------------------
93+
| Introspection
94+
|--------------------------------------------------------------------------
95+
|
96+
| Introspection queries allow a user to see the schema and will power
97+
| development tools. This is "auto" by default, which will enable
98+
| it locally and keep it disabled everywhere else for security.
99+
|
100+
*/
101+
102+
'introspection' => env('STATAMIC_GRAPHQL_INTROSPECTION_ENABLED', 'auto'),
103+
91104
];

resources/views/graphql/graphiql.blade.php

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
<style>
88
body {
99
margin: 0;
10-
overflow: hidden; /* in Firefox */
1110
}
1211
1312
#graphiql {
@@ -24,63 +23,63 @@
2423
</style>
2524
<link
2625
rel="stylesheet"
27-
href="https://esm.sh/graphiql@4.0.0/dist/style.css"
26+
href="https://esm.sh/graphiql/dist/style.css"
2827
>
2928
<link
3029
rel="stylesheet"
31-
href="https://esm.sh/@graphiql/plugin-explorer@4.0.0/dist/style.css"
30+
href="https://esm.sh/@graphiql/plugin-explorer/dist/style.css"
3231
>
33-
<!-- Note: the ?standalone flag bundles the module along with all of its `dependencies`, excluding `peerDependencies`, into a single JavaScript file. -->
32+
33+
@if (!$introspection)
34+
<style>
35+
button[aria-label*="Re-fetch GraphQL schema"] { visibility: hidden; }
36+
</style>
37+
@endif
38+
3439
<script type="importmap">
3540
{
3641
"imports": {
3742
"react": "https://esm.sh/react@19.1.0",
38-
"react/jsx-runtime": "https://esm.sh/react@19.1.0/jsx-runtime",
43+
"react/": "https://esm.sh/react@19.1.0/",
3944
4045
"react-dom": "https://esm.sh/react-dom@19.1.0",
41-
"react-dom/client": "https://esm.sh/react-dom@19.1.0/client",
46+
"react-dom/": "https://esm.sh/react-dom@19.1.0/",
4247
43-
"graphiql": "https://esm.sh/graphiql@4.0.0?standalone&external=react,react/jsx-runtime,react-dom,@graphiql/react",
44-
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer@4.0.0?standalone&external=react,react/jsx-runtime,react-dom,@graphiql/react,graphql",
45-
"@graphiql/react": "https://esm.sh/@graphiql/react@0.30.0?standalone&external=react,react/jsx-runtime,react-dom,graphql,@graphiql/toolkit",
48+
"graphiql": "https://esm.sh/graphiql?standalone&external=react,react-dom,@graphiql/react,graphql",
49+
"graphiql/": "https://esm.sh/graphiql/",
50+
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer?standalone&external=react,@graphiql/react,graphql",
51+
"@graphiql/react": "https://esm.sh/@graphiql/react?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
4652
47-
"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.11.2?standalone&external=graphql",
48-
"graphql": "https://esm.sh/graphql@16.11.0"
53+
"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit?standalone&external=graphql",
54+
"graphql": "https://esm.sh/graphql@16.11.0",
55+
"@emotion/is-prop-valid": "data:text/javascript,"
4956
}
5057
}
5158
</script>
5259
<script type="module">
53-
// Import React and ReactDOM
5460
import React from 'react';
5561
import ReactDOM from 'react-dom/client';
56-
// Import GraphiQL and the Explorer plugin
57-
import { GraphiQL } from 'graphiql';
62+
import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
5863
import { createGraphiQLFetcher } from '@graphiql/toolkit';
5964
import { explorerPlugin } from '@graphiql/plugin-explorer';
65+
import 'graphiql/setup-workers/esm.sh';
6066
61-
var xcsrfToken = null;
67+
const introspectionEnabled = {{ \Statamic\Support\Str::bool($introspection) }};
6268
6369
const fetcher = createGraphiQLFetcher({
6470
url: '{{ $url }}',
65-
headers: {
66-
Accept: 'application/json',
67-
'Content-Type': 'application/json',
68-
'x-csrf-token': xcsrfToken || '{{ csrf_token() }}',
69-
},
70-
fetch: async (fetchURL, fetchOptions) => {
71-
return await fetch(fetchURL, fetchOptions).then((response) => {
72-
xcsrfToken = response.headers.get('x-csrf-token');
73-
return response;
74-
});
75-
},
7671
});
7772
78-
const explorer = explorerPlugin();
73+
let plugins = [HISTORY_PLUGIN];
74+
if (introspectionEnabled) plugins.push(explorerPlugin());
7975
8076
function App() {
8177
return React.createElement(GraphiQL, {
8278
fetcher,
83-
plugins: [explorer],
79+
plugins,
80+
defaultEditorToolsVisibility: true,
81+
referencePlugin: introspectionEnabled ? undefined : null,
82+
schema: introspectionEnabled ? undefined : null,
8483
});
8584
}
8685

src/Facades/GraphQL.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* @method static array getExtraQueries()
2323
* @method static void addMiddleware($middleware)
2424
* @method static array getExtraMiddleware()
25+
* @method static bool introspectionEnabled()
2526
*
2627
* @see \Statamic\GraphQL\Manager
2728
*/

src/GraphQL/Manager.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,15 @@ public function getExtraMiddleware()
9595
{
9696
return $this->middleware;
9797
}
98+
99+
public function introspectionEnabled(): bool
100+
{
101+
if (config('graphql.security.disable_introspection')) {
102+
return false;
103+
}
104+
105+
$config = config('statamic.graphql.introspection') ?? 'auto';
106+
107+
return $config === 'auto' ? app()->isLocal() : (bool) $config;
108+
}
98109
}

src/GraphQL/ServiceProvider.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Support\ServiceProvider as LaravelProvider;
77
use Rebing\GraphQL\GraphQLController;
88
use Statamic\Contracts\GraphQL\ResponseCache;
9+
use Statamic\Facades\GraphQL;
910
use Statamic\GraphQL\ResponseCache\DefaultCache;
1011
use Statamic\GraphQL\ResponseCache\NullCache;
1112
use Statamic\Http\Middleware\HandleToken;
@@ -32,6 +33,7 @@ public function register()
3233

3334
$this->disableGraphiql();
3435
$this->setDefaultSchema();
36+
$this->configureIntrospection();
3537
});
3638
}
3739

@@ -71,4 +73,9 @@ private function setDefaultSchema()
7173
{
7274
config(['graphql.schemas.default' => DefaultSchema::class]);
7375
}
76+
77+
private function configureIntrospection()
78+
{
79+
config(['graphql.security.disable_introspection' => ! GraphQL::introspectionEnabled()]);
80+
}
7481
}

src/Http/Controllers/CP/GraphQLController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Statamic\Http\Controllers\CP;
44

5+
use Statamic\Facades\GraphQL;
56
use Statamic\Http\Middleware\RequireStatamicPro;
67

78
class GraphQLController extends CpController
@@ -22,6 +23,7 @@ public function graphiql()
2223

2324
return view('statamic::graphql.graphiql', [
2425
'url' => '/'.config('graphql.route.prefix'),
26+
'introspection' => GraphQL::introspectionEnabled(),
2527
]);
2628
}
2729
}

tests/GraphQL/ManagerTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Tests\GraphQL;
4+
5+
use PHPUnit\Framework\Attributes\DataProvider;
6+
use PHPUnit\Framework\Attributes\Group;
7+
use PHPUnit\Framework\Attributes\Test;
8+
use Statamic\Facades\GraphQL;
9+
use Tests\TestCase;
10+
11+
#[Group('graphql')]
12+
class ManagerTest extends TestCase
13+
{
14+
#[Test]
15+
#[DataProvider('introspectionProvider')]
16+
public function it_gets_introspection_enabled_state($packageEnabled, $statamicConfig, $environment, $expected)
17+
{
18+
config(['graphql.security.disable_introspection' => is_null($packageEnabled) ? null : ! $packageEnabled]);
19+
config(['statamic.graphql.introspection' => $statamicConfig]);
20+
$this->app['env'] = $environment;
21+
22+
$this->assertEquals($expected, GraphQL::introspectionEnabled());
23+
}
24+
25+
public static function introspectionProvider()
26+
{
27+
return [
28+
'pkg null, statamic null, local' => [null, null, 'local', true],
29+
'pkg null, statamic null, prod' => [null, null, 'prod', false],
30+
31+
'pkg enabled, statamic null, local' => [true, null, 'local', true],
32+
'pkg enabled, statamic null, prod' => [true, null, 'prod', false],
33+
'pkg enabled, statamic auto, local' => [true, 'auto', 'local', true],
34+
'pkg enabled, statamic auto, prod' => [true, 'auto', 'prod', false],
35+
'pkg enabled, statamic enabled, local' => [true, true, 'local', true],
36+
'pkg enabled, statamic enabled, prod' => [true, true, 'prod', true],
37+
'pkg enabled, statamic disabled, local' => [true, false, 'local', false],
38+
'pkg enabled, statamic disabled, prod' => [true, false, 'prod', false],
39+
40+
'pkg disabled, statamic null, local' => [false, null, 'local', false],
41+
'pkg disabled, statamic null, prod' => [false, null, 'prod', false],
42+
'pkg disabled, statamic auto, local' => [false, 'auto', 'local', false],
43+
'pkg disabled, statamic auto, prod' => [false, 'auto', 'prod', false],
44+
'pkg disabled, statamic enabled, local' => [false, true, 'local', false],
45+
'pkg disabled, statamic enabled, prod' => [false, true, 'prod', false],
46+
'pkg disabled, statamic disabled, local' => [false, false, 'local', false],
47+
'pkg disabled, statamic disabled, prod' => [false, false, 'prod', false],
48+
];
49+
}
50+
}

0 commit comments

Comments
 (0)