From eb7ccc9cde89d7a0d63c9984f676e3d30557a716 Mon Sep 17 00:00:00 2001 From: Aaron Carlino Date: Thu, 23 Dec 2021 13:28:19 +1300 Subject: [PATCH] NEW: Obfuscate filenames to prevent IDE search issues --- _config/config.yml | 22 +++++++++ _config/tests.yml | 8 ++++ src/Dev/State/DebugSchemaState.php | 34 ++++++++++++++ src/Schema/Storage/AbstractTypeRegistry.php | 8 +++- src/Schema/Storage/CodeGenerationStore.php | 46 ++++++++++++++++++- src/Schema/Storage/HashNameObfuscator.php | 19 ++++++++ src/Schema/Storage/HybridObfuscator.php | 19 ++++++++ src/Schema/Storage/NaiveNameObfuscator.php | 19 ++++++++ src/Schema/Storage/NameObfuscator.php | 16 +++++++ src/Schema/Storage/templates/enum.inc.php | 4 +- .../Storage/templates/interface.inc.php | 4 +- src/Schema/Storage/templates/scalar.inc.php | 4 +- src/Schema/Storage/templates/type.inc.php | 4 +- src/Schema/Storage/templates/union.inc.php | 4 +- src/Schema/Type/Enum.php | 10 ++++ tests/Schema/IntegrationTest.php | 38 ++++++++++++++- tests/Schema/TestStoreCreator.php | 2 +- 17 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 _config/tests.yml create mode 100644 src/Dev/State/DebugSchemaState.php create mode 100644 src/Schema/Storage/HashNameObfuscator.php create mode 100644 src/Schema/Storage/HybridObfuscator.php create mode 100644 src/Schema/Storage/NaiveNameObfuscator.php create mode 100644 src/Schema/Storage/NameObfuscator.php diff --git a/_config/config.yml b/_config/config.yml index cc1b9e0ab..b2be377bc 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -33,6 +33,9 @@ SilverStripe\Core\Injector\Injector: constructor: storeCreator: '%$SilverStripe\GraphQL\Schema\Interfaces\SchemaStorageCreator' + SilverStripe\GraphQL\Schema\Storage\NameObfuscator: + class: SilverStripe\GraphQL\Schema\Storage\HybridObfuscator + SilverStripe\GraphQL\Schema\Schema: schemas: [] --- @@ -49,3 +52,22 @@ Only: SilverStripe\TestSession\TestSessionEnvironment: extensions: - SilverStripe\GraphQL\Extensions\TestSessionEnvironmentExtension + + +--- +Name: graphqlconfig-debug +Only: + envvarset: 'DEBUG_SCHEMA' +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\GraphQL\Schema\Storage\NameObfuscator: + class: SilverStripe\GraphQL\Schema\Storage\NaiveNameObfuscator + +--- +Name: graphqlconfig-live +Except: + environment: dev +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\GraphQL\Schema\Storage\NameObfuscator: + class: SilverStripe\GraphQL\Schema\Storage\HashNameObfuscator diff --git a/_config/tests.yml b/_config/tests.yml new file mode 100644 index 000000000..a9023d1a8 --- /dev/null +++ b/_config/tests.yml @@ -0,0 +1,8 @@ +--- +Name: graphql-test +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\Dev\State\SapphireTestState: + properties: + States: + debugSchema: '%$SilverStripe\GraphQL\Dev\State\DebugSchemaState' diff --git a/src/Dev/State/DebugSchemaState.php b/src/Dev/State/DebugSchemaState.php new file mode 100644 index 000000000..4106a9bdf --- /dev/null +++ b/src/Dev/State/DebugSchemaState.php @@ -0,0 +1,34 @@ +get(NameObfuscator::class); $type = null; if (!isset(static::$types[$typename])) { - $file = static::getSourceDirectory() . DIRECTORY_SEPARATOR . $typename . '.php'; + $obfuscatedName = $obfuscator->obfuscate($typename); + $file = static::getSourceDirectory() . DIRECTORY_SEPARATOR . $obfuscatedName . '.php'; if (file_exists($file)) { require_once($file); - $cls = static::getSourceNamespace() . '\\' . $typename; + $cls = static::getSourceNamespace() . '\\' . $obfuscatedName; if (class_exists($cls)) { $type = new $cls(); } diff --git a/src/Schema/Storage/CodeGenerationStore.php b/src/Schema/Storage/CodeGenerationStore.php index 717d5e724..bbcb6b534 100644 --- a/src/Schema/Storage/CodeGenerationStore.php +++ b/src/Schema/Storage/CodeGenerationStore.php @@ -8,6 +8,7 @@ use Psr\SimpleCache\CacheInterface; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Injector\Injectable; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Path; use SilverStripe\GraphQL\Schema\Exception\EmptySchemaException; use SilverStripe\GraphQL\Schema\Exception\SchemaNotFoundException; @@ -60,6 +61,13 @@ class CodeGenerationStore implements SchemaStorageInterface */ private static $dirName = '.graphql'; + /** + * @var string[] + */ + private static $dependencies = [ + 'Obfuscator' => '%$' . NameObfuscator::class, + ]; + /** * @var string */ @@ -85,6 +93,11 @@ class CodeGenerationStore implements SchemaStorageInterface */ private $graphqlSchema; + /** + * @var NameObfuscator + */ + private $obfuscator; + /** * @param string $name * @param CacheInterface $cache @@ -140,9 +153,11 @@ public function persistSchema(StorableSchema $schema): void } $templateDir = static::getTemplateDir(); + $obfuscator = $this->getObfuscator(); $globals = [ 'typeClassName' => self::TYPE_CLASS_NAME, 'namespace' => $this->getNamespace(), + 'obfuscator' => $obfuscator, ]; $config = $schema->getConfig()->toArray(); @@ -209,7 +224,8 @@ public function persistSchema(StorableSchema $schema): void continue; } } - $file = Path::join($temp, $name . '.php'); + $obfuscatedName = $obfuscator->obfuscate($name); + $file = Path::join($temp, $obfuscatedName . '.php'); $encoder = Encoder::create(Path::join($templateDir, $template), $type, $globals); $code = $encoder->encode(); $fs->dumpFile($file, $this->toCode($code)); @@ -229,7 +245,14 @@ public function persistSchema(StorableSchema $schema): void /* @var SplFileInfo $file */ foreach ($currentFiles as $file) { - $type = $file->getBasename('.php'); + $contents = $file->getContents(); + preg_match('/\/\/ @type:([A-Za-z0-9+_]+)/', $contents, $matches); + Schema::invariant( + $matches, + 'Could not find type name in file %s', + $file->getPathname() + ); + $type = $matches[1]; if (!in_array($type, $touched)) { $fs->remove($file->getPathname()); $this->getCache()->delete($type); @@ -384,6 +407,25 @@ public function setRootDir(string $rootDir): CodeGenerationStore return $this; } + /** + * @return NameObfuscator + */ + public function getObfuscator(): NameObfuscator + { + return $this->obfuscator; + } + + /** + * @param NameObfuscator $obfuscator + * @return CodeGenerationStore + */ + public function setObfuscator(NameObfuscator $obfuscator): CodeGenerationStore + { + $this->obfuscator = $obfuscator; + return $this; + } + + /** * @return string */ diff --git a/src/Schema/Storage/HashNameObfuscator.php b/src/Schema/Storage/HashNameObfuscator.php new file mode 100644 index 000000000..ab35359ae --- /dev/null +++ b/src/Schema/Storage/HashNameObfuscator.php @@ -0,0 +1,19 @@ +getName(); ?> extends EnumType +// @type:getName(); ?> + +class obfuscate($enum->getName()) ?> extends EnumType { public function __construct() { diff --git a/src/Schema/Storage/templates/interface.inc.php b/src/Schema/Storage/templates/interface.inc.php index 4f412ec95..f82b5ebbb 100644 --- a/src/Schema/Storage/templates/interface.inc.php +++ b/src/Schema/Storage/templates/interface.inc.php @@ -9,7 +9,9 @@ use GraphQL\Type\Definition\InterfaceType; use SilverStripe\GraphQL\Schema\Resolver\ComposedResolver; -class getName(); ?> extends InterfaceType +// @type:getName(); ?> + +class obfuscate($interface->getName()) ?> extends InterfaceType { public function __construct() { diff --git a/src/Schema/Storage/templates/scalar.inc.php b/src/Schema/Storage/templates/scalar.inc.php index 2e7593964..1645d7e26 100644 --- a/src/Schema/Storage/templates/scalar.inc.php +++ b/src/Schema/Storage/templates/scalar.inc.php @@ -8,7 +8,9 @@ use GraphQL\Type\Definition\CustomScalarType; -class getName(); ?> extends CustomScalarType +// @type:getName(); ?> + +class obfuscate($scalar->getName()) ?> extends CustomScalarType { public function __construct() { diff --git a/src/Schema/Storage/templates/type.inc.php b/src/Schema/Storage/templates/type.inc.php index 481505bc4..3a711b312 100644 --- a/src/Schema/Storage/templates/type.inc.php +++ b/src/Schema/Storage/templates/type.inc.php @@ -10,8 +10,10 @@ use GraphQL\Type\Definition\InputObjectType; use SilverStripe\GraphQL\Schema\Resolver\ComposedResolver; +// @type:getName(); ?> -class getName() ?> extends getIsInput()) : + +class obfuscate($type->getName()) ?> extends getIsInput()) : ?>InputObjectTypeObjectTypegetName() ?> extends UnionType +// @type:getName(); ?> + +class obfuscate($union->getName()) ?> extends UnionType { public function __construct() { diff --git a/src/Schema/Type/Enum.php b/src/Schema/Type/Enum.php index 86a990f49..d533d980d 100644 --- a/src/Schema/Type/Enum.php +++ b/src/Schema/Type/Enum.php @@ -84,6 +84,16 @@ public function validate(): void 'Enum type %s has no values defined', $this->getName() ); + $rx = '/^[_a-zA-Z][_a-zA-Z0-9]*$/'; + foreach ($this->getValueList() as $item) { + Schema::invariant( + preg_match($rx, $item['Key']), + 'Key "%s" for "%s" is not valid. Must match %s', + $item['Key'], + $this->getName(), + $rx + ); + } } /** diff --git a/tests/Schema/IntegrationTest.php b/tests/Schema/IntegrationTest.php index 3424ce37e..eccc8183d 100644 --- a/tests/Schema/IntegrationTest.php +++ b/tests/Schema/IntegrationTest.php @@ -7,6 +7,7 @@ use SilverStripe\Assets\File; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Core\Path; use SilverStripe\Dev\SapphireTest; use SilverStripe\GraphQL\QueryHandler\QueryHandler; use SilverStripe\GraphQL\QueryHandler\SchemaConfigProvider; @@ -17,6 +18,8 @@ use SilverStripe\GraphQL\Schema\SchemaBuilder; use SilverStripe\GraphQL\Schema\Storage\CodeGenerationStore; use SilverStripe\GraphQL\Schema\Storage\CodeGenerationStoreCreator; +use SilverStripe\GraphQL\Schema\Storage\HashNameObfuscator; +use SilverStripe\GraphQL\Schema\Storage\NameObfuscator; use SilverStripe\GraphQL\Tests\Fake\DataObjectFake; use SilverStripe\GraphQL\Tests\Fake\FakePage; use SilverStripe\GraphQL\Tests\Fake\FakeProduct; @@ -767,10 +770,35 @@ public function testBasicPaginator() ], $records); } - public function testQueriesAndMutations() + /** + * @throws SchemaBuilderException + * @throws SchemaNotFoundException + * @dataProvider provideObfuscationState + * @param bool $shouldObfuscateTypes + */ + public function testQueriesAndMutations($shouldObfuscateTypes) { + FakeProductPage::get()->removeAll(); + if ($shouldObfuscateTypes) { + Injector::inst()->load([ + NameObfuscator::class => [ + 'class' => HashNameObfuscator::class, + ] + ]); + } $schema = $this->createSchema(new TestSchemaBuilder(['_' . __FUNCTION__])); + if ($shouldObfuscateTypes) { + $obfuscator = new HashNameObfuscator(); + $obfuscatedName = $obfuscator->obfuscate('FakeProductPage'); + $path = Path::join( + __DIR__, + CodeGenerationStore::config()->get('dirName'), + $schema->getSchemaKey(), + $obfuscatedName . '.php' + ); + $this->assertTrue(file_exists($path)); + } // Create a couple of product pages $productPageIDs = []; foreach (range(1, 2) as $num) { @@ -1028,6 +1056,14 @@ public function testDBFieldArgs() $this->assertEquals('This is a really long text field. It has a few sentences.', $node['myText']); } + /** + * @return array + */ + public function provideObfuscationState(): array + { + return [ [false], [true] ]; + } + /** * @param TestSchemaBuilder $factory * @return Schema diff --git a/tests/Schema/TestStoreCreator.php b/tests/Schema/TestStoreCreator.php index 86e99b1c5..a65bb83d3 100644 --- a/tests/Schema/TestStoreCreator.php +++ b/tests/Schema/TestStoreCreator.php @@ -21,7 +21,7 @@ class TestStoreCreator implements SchemaStorageCreator public function createStore(string $name): SchemaStorageInterface { - $store = new CodeGenerationStore($name, new FilesystemCache()); + $store = CodeGenerationStore::create($name, new FilesystemCache()); $store->setRootDir(static::$dir); return $store;