From 215dd863da1d2f81d7837e7f31649290d6eadfe0 Mon Sep 17 00:00:00 2001 From: andris-sevcenko Date: Thu, 16 Jul 2020 16:13:21 +0300 Subject: [PATCH 1/3] =?UTF-8?q?Store=20public=20schema=E2=80=99s=20token?= =?UTF-8?q?=20settings=20in=20the=20project=20config.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it possible to tweak the public access settings on sites with `allowAdminChanges` setting set to `true`. Resolve #6078 --- CHANGELOG-v3.5.md | 1 + CHANGELOG-v3.md | 1 + src/base/ApplicationTrait.php | 6 ++ src/config/app.php | 2 +- ...ublic_token_settings_in_project_config.php | 62 +++++++++++++ src/services/Gql.php | 88 +++++++++++++++---- src/services/ProjectConfig.php | 25 +++++- 7 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 src/migrations/m200716_153800_public_token_settings_in_project_config.php diff --git a/CHANGELOG-v3.5.md b/CHANGELOG-v3.5.md index fe8d5b72f9e..bf92232dc90 100644 --- a/CHANGELOG-v3.5.md +++ b/CHANGELOG-v3.5.md @@ -374,6 +374,7 @@ - Improved `data`/`aria` tag normalization via `craft\helpers\Html::parseTagAttributes()` and `normalizeTagAttributes()`. - Control panel form input macros and templates that accept a `class` variable can now pass it as an array of class names. - Control panel templates can now set a `formActions` variable, which registers alternative Save menu actions, optionally with associated keyboard shortcuts. +- The GraphQL public schema token settings are now stored in the project config. ([#6078](https://github.com/craftcms/cms/issues/6078)) - The `_layouts/base` template now supports a `bodyAttributes` variable. - The `Craft.cp.submitPrimaryForm()` method now accepts an `options` argument for customizing the form submit. - Updated Yii to 2.0.36. diff --git a/CHANGELOG-v3.md b/CHANGELOG-v3.md index 11e0e70c555..089752845b5 100644 --- a/CHANGELOG-v3.md +++ b/CHANGELOG-v3.md @@ -94,6 +94,7 @@ - The `graphql/api` and `live-preview/preview` actions no longer add CORS headers that were already set on the response. ([#6355](https://github.com/craftcms/cms/issues/6355)) - `craft\elements\db\ElementQuery::$enabledForSite` is now set to `false` by default, leaving it up to elements’ status conditions to factor in the site-specific element statuses. ([#6273](https://github.com/craftcms/cms/issues/6273)) - `craft\services\ProjectConfig::CONFIG_FILENAME` is no longer deprecated. +- The GraphQL public schema token settings are now stored in the project config. ([#6078](https://github.com/craftcms/cms/issues/6078)) - The `_layouts/base` template now supports a `bodyAttributes` variable. - Updated Yii to 2.0.36. - Updated PrismJS to 1.20.0. diff --git a/src/base/ApplicationTrait.php b/src/base/ApplicationTrait.php index 24a0633eb04..74ba5626e05 100644 --- a/src/base/ApplicationTrait.php +++ b/src/base/ApplicationTrait.php @@ -1638,5 +1638,11 @@ private function _registerConfigListeners() ->onAdd(Gql::CONFIG_GQL_SCHEMAS_KEY . '.{uid}', [$gqlService, 'handleChangedSchema']) ->onUpdate(Gql::CONFIG_GQL_SCHEMAS_KEY . '.{uid}', [$gqlService, 'handleChangedSchema']) ->onRemove(Gql::CONFIG_GQL_SCHEMAS_KEY . '.{uid}', [$gqlService, 'handleDeletedSchema']); + + // GraphQL public token + $projectConfigService + ->onAdd(Gql::CONFIG_GQL_PUBLIC_TOKEN_KEY, [$gqlService, 'handleChangedPublicToken']) + ->onUpdate(Gql::CONFIG_GQL_PUBLIC_TOKEN_KEY, [$gqlService, 'handleChangedPublicToken']); + } } diff --git a/src/config/app.php b/src/config/app.php index b280bf83bd9..027e2b43a94 100644 --- a/src/config/app.php +++ b/src/config/app.php @@ -4,7 +4,7 @@ 'id' => 'CraftCMS', 'name' => 'Craft CMS', 'version' => '3.5.0-beta.3', - 'schemaVersion' => '3.5.10', + 'schemaVersion' => '3.5.11', 'minVersionRequired' => '2.6.2788', 'basePath' => dirname(__DIR__), // Defines the @app alias 'runtimePath' => '@storage/runtime', // Defines the @runtime alias diff --git a/src/migrations/m200716_153800_public_token_settings_in_project_config.php b/src/migrations/m200716_153800_public_token_settings_in_project_config.php new file mode 100644 index 00000000000..578eef349d3 --- /dev/null +++ b/src/migrations/m200716_153800_public_token_settings_in_project_config.php @@ -0,0 +1,62 @@ +getProjectConfig(); + $schemaVersion = $projectConfig->get('system.schemaVersion', true); + + if (version_compare($schemaVersion, '3.5.11', '<')) { + // Default settings, is no public token set. + $data = [ + 'enabled' => false, + 'expiryDate' => null, + ]; + + $publicToken = (new Query()) + ->select([ + 'enabled', + 'expiryDate', + ]) + ->from([Table::GQLTOKENS]) + ->where(['accessToken' => GqlToken::PUBLIC_TOKEN]) + ->one(); + + // If a public schema token existed, use those settings. + if ($publicToken) { + $data['expiryDate'] = $publicToken['expiryDate'] ? DateTimeHelper::toDateTime($publicToken['expiryDate'])->getTimestamp() : null; + $data['enabled'] = (bool)$publicToken['enabled']; + } + + // This will ensure that a public schema token is created on sites where it did not exist. It'll just be disabled. + Craft::$app->getProjectConfig()->set(Gql::CONFIG_GQL_PUBLIC_TOKEN_KEY, $data); + } + } + + /** + * @inheritdoc + */ + public function safeDown() + { + echo "m200716_153800_public_token_settings_in_project_config cannot be reverted.\n"; + return false; + } +} diff --git a/src/services/Gql.php b/src/services/Gql.php index c24b1db8040..87b91da1bbc 100644 --- a/src/services/Gql.php +++ b/src/services/Gql.php @@ -58,6 +58,7 @@ use craft\gql\types\Number; use craft\gql\types\Query; use craft\gql\types\QueryArgument; +use craft\helpers\DateTimeHelper; use craft\helpers\Db; use craft\helpers\Gql as GqlHelper; use craft\helpers\Json; @@ -269,6 +270,11 @@ class Gql extends Component */ const CONFIG_GQL_SCHEMAS_KEY = 'graphql.schemas'; + /** + * @since 3.5.0 + */ + const CONFIG_GQL_PUBLIC_TOKEN_KEY = 'graphql.publicToken'; + /** * The field name to use when fetching count of related elements * @@ -276,6 +282,36 @@ class Gql extends Component */ const GRAPHQL_COUNT_FIELD = '_count'; + /** + * Save a GQL Token record based on the model. + * + * @param GqlToken $token + */ + protected function saveTokenInternal(GqlToken $token): void + { + $isNewToken = !$token->id; + + if ($isNewToken) { + $tokenRecord = new GqlTokenRecord(); + } else { + $tokenRecord = GqlTokenRecord::findOne($token->id) ?: new GqlTokenRecord(); + } + + $tokenRecord->name = $token->name; + $tokenRecord->enabled = (bool)$token->enabled; + $tokenRecord->expiryDate = $token->expiryDate; + $tokenRecord->lastUsed = $token->lastUsed; + $tokenRecord->schemaId = $token->schemaId; + + if ($token->accessToken) { + $tokenRecord->accessToken = $token->accessToken; + } + + $tokenRecord->save(); + $token->id = $tokenRecord->id; + $token->uid = $tokenRecord->uid; + } + /** * @var Schema Currently loaded schema definition */ @@ -305,7 +341,6 @@ public function getSchemaDef(GqlSchema $schema = null, $prebuildSchema = false): if ($schema) { $this->setActiveSchema($schema); } - if (!$this->_schemaDef || $prebuildSchema) { // Either cached version was not found or we need a pre-built schema. $registeredTypes = $this->_registerGqlTypes(); @@ -762,34 +797,51 @@ public function saveToken(GqlToken $token, $runValidation = true): bool return false; } - $isNewToken = !$token->id; + // Public token information is stored in the project config + if ($token->accessToken === GqlToken::PUBLIC_TOKEN) { + $data = [ + 'expiryDate' => $token->expiryDate ? $token->expiryDate->getTimestamp() : null, + 'enabled' => (bool)$token->enabled + ]; + + Craft::$app->getProjectConfig()->set(self::CONFIG_GQL_PUBLIC_TOKEN_KEY, $data); + + return true; + } if ($runValidation && !$token->validate()) { Craft::info('Token not saved due to validation error.', __METHOD__); return false; } - if ($isNewToken) { - $tokenRecord = new GqlTokenRecord(); - } else { - $tokenRecord = GqlTokenRecord::findOne($token->id) ?: new GqlTokenRecord(); - } + $this->saveTokenInternal($token); - $tokenRecord->name = $token->name; - $tokenRecord->enabled = (bool)$token->enabled; - $tokenRecord->expiryDate = $token->expiryDate; - $tokenRecord->lastUsed = $token->lastUsed; - $tokenRecord->schemaId = $token->schemaId; + return true; + } - if ($token->accessToken) { - $tokenRecord->accessToken = $token->accessToken; + /** + * Handle public token settings being updated. + * @param ConfigEvent $event + * + * @since 3.5.0 + */ + public function handleChangedPublicToken(ConfigEvent $event) + { + $data = $event->newValue; + + try { + $token = $this->getTokenByAccessToken(GqlToken::PUBLIC_TOKEN); + } catch (InvalidArgumentException $exception) { + $token = new GqlToken([ + 'name' => 'Public Token', + 'accessToken' => GqlToken::PUBLIC_TOKEN, + ]); } - $tokenRecord->save(); - $token->id = $tokenRecord->id; - $token->uid = $tokenRecord->uid; + $token->expiryDate = $data['expiryDate'] ? DateTimeHelper::toDateTime($data['expiryDate']): null; + $token->enabled = $data['enabled'] ?: false; - return true; + $this->saveTokenInternal($token); } /** diff --git a/src/services/ProjectConfig.php b/src/services/ProjectConfig.php index 826d6ccb904..c1e9bfbea0c 100644 --- a/src/services/ProjectConfig.php +++ b/src/services/ProjectConfig.php @@ -21,6 +21,7 @@ use craft\helpers\Json; use craft\helpers\ProjectConfig as ProjectConfigHelper; use craft\helpers\StringHelper; +use craft\models\GqlToken; use Symfony\Component\Yaml\Yaml; use yii\base\Application; use yii\base\Component; @@ -2430,7 +2431,29 @@ private function _getGqlData(): array $row['scope'] = Json::decodeIfJson($row['scope']); } - return ['schemas' => $scopeRows]; + $output = [ + 'schemas' => $scopeRows, + 'publicToken' => [ + 'enabled' => false, + 'expiryDate' => null, + ] + ]; + + $publicToken = (new Query()) + ->select([ + 'enabled', + 'expiryDate', + ]) + ->from([Table::GQLTOKENS]) + ->where(['accessToken' => GqlToken::PUBLIC_TOKEN]) + ->one(); + + if ($publicToken) { + $output['publicToken']['expiryDate'] = $publicToken['expiryDate'] ? DateTimeHelper::toDateTime($publicToken['expiryDate'])->getTimestamp() : null; + $output['publicToken']['enabled'] = (bool)$publicToken['enabled']; + } + + return $output; } /** From 5502232a2c37206bd04366097ff15697810ba5a0 Mon Sep 17 00:00:00 2001 From: Brandon Kelly Date: Thu, 16 Jul 2020 11:42:46 -0700 Subject: [PATCH 2/3] private _saveTokenInternal() --- src/services/Gql.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/services/Gql.php b/src/services/Gql.php index 87b91da1bbc..b9a423f6357 100644 --- a/src/services/Gql.php +++ b/src/services/Gql.php @@ -287,7 +287,7 @@ class Gql extends Component * * @param GqlToken $token */ - protected function saveTokenInternal(GqlToken $token): void + private function _saveTokenInternal(GqlToken $token) { $isNewToken = !$token->id; @@ -694,8 +694,6 @@ public function getAllSchemaComponents(): array /** * Flush all GraphQL caches, registries and loaders. - * - * @return void */ public function flushCaches() { @@ -814,7 +812,7 @@ public function saveToken(GqlToken $token, $runValidation = true): bool return false; } - $this->saveTokenInternal($token); + $this->_saveTokenInternal($token); return true; } @@ -841,7 +839,7 @@ public function handleChangedPublicToken(ConfigEvent $event) $token->expiryDate = $data['expiryDate'] ? DateTimeHelper::toDateTime($data['expiryDate']): null; $token->enabled = $data['enabled'] ?: false; - $this->saveTokenInternal($token); + $this->_saveTokenInternal($token); } /** @@ -1181,8 +1179,6 @@ private function _registerGqlTypes(): array /** * Get GraphQL query definitions - * - * @return void */ private function _registerGqlQueries() { @@ -1211,8 +1207,6 @@ private function _registerGqlQueries() /** * Get GraphQL mutation definitions - * - * @return void */ private function _registerGqlMutations() { From c301c6d9f730d19d78c39294d4ab87422f86887f Mon Sep 17 00:00:00 2001 From: Brandon Kelly Date: Thu, 16 Jul 2020 12:05:40 -0700 Subject: [PATCH 3/3] Update changelog --- CHANGELOG-v3.5.md | 4 +++- CHANGELOG-v3.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-v3.5.md b/CHANGELOG-v3.5.md index bd14d3d63b1..3490b01c67b 100644 --- a/CHANGELOG-v3.5.md +++ b/CHANGELOG-v3.5.md @@ -270,7 +270,9 @@ - Added `craft\services\Elements::stopCollectingCacheTags()`. - Added `craft\services\Fields::createLayoutElement()`. - Added `craft\services\Fields::getLayoutsByElementType()`. +- Added `craft\services\Gql::CONFIG_GQL_PUBLIC_TOKEN_KEY`. - Added `craft\services\Gql::getAllSchemaComponents()`. +- Added `craft\services\Gql::handleChangedPublicToken()`. - Added `craft\services\Images::getSupportsWebP()`. ([#5853](https://github.com/craftcms/cms/issues/5853)) - Added `craft\services\Path::getProjectConfigPath()`. - Added `craft\services\ProjectConfig::$folderName`. ([#5982](https://github.com/craftcms/cms/issues/5982)) @@ -303,6 +305,7 @@ ### Changed - Craft now stores project config files in a new `config/project/` folder, regardless of whether the (deprecated) `useProjectConfigFile` config setting is enabled, and syncing new project config file changes is now optional. +- The public GraphQL schema’s access settings are now stored in the project config. ([#6078](https://github.com/craftcms/cms/issues/6078)) - User registration forms in the control panel now give users the option to send an activation email, even if email verification isn’t required. ([#5836](https://github.com/craftcms/cms/issues/5836)) - Activation emails are now sent automatically on public registration if the `deferPublicRegistrationPassword` config setting is enabled, even if email verification isn’t required. ([#5836](https://github.com/craftcms/cms/issues/5836)) - Craft now remembers the selected site across global sets and element indexes. ([#2779](https://github.com/craftcms/cms/issues/2779)) @@ -376,7 +379,6 @@ - Improved `data`/`aria` tag normalization via `craft\helpers\Html::parseTagAttributes()` and `normalizeTagAttributes()`. - Control panel form input macros and templates that accept a `class` variable can now pass it as an array of class names. - Control panel templates can now set a `formActions` variable, which registers alternative Save menu actions, optionally with associated keyboard shortcuts. -- The GraphQL public schema token settings are now stored in the project config. ([#6078](https://github.com/craftcms/cms/issues/6078)) - The `_layouts/base` template now supports a `bodyAttributes` variable. - The `Craft.cp.submitPrimaryForm()` method now accepts an `options` argument for customizing the form submit. - Updated Yii to 2.0.36. diff --git a/CHANGELOG-v3.md b/CHANGELOG-v3.md index 8a61322749d..afbbe53adf8 100644 --- a/CHANGELOG-v3.md +++ b/CHANGELOG-v3.md @@ -69,6 +69,8 @@ - Added `craft\models\FieldLayoutTab::getElementConfigs()`. - Added `craft\models\FieldLayoutTab::updateConfig()`. - Added `craft\services\Fields::createLayoutElement()`. +- Added `craft\services\Gql::CONFIG_GQL_PUBLIC_TOKEN_KEY`. +- Added `craft\services\Gql::handleChangedPublicToken()`. - Added `craft\services\Path::getProjectConfigPath()`. - Added `craft\services\ProjectConfig::$folderName`. ([#5982](https://github.com/craftcms/cms/issues/5982)) - Added `craft\web\Controller::setFailFlash()`. @@ -83,6 +85,7 @@ ### Changed - Craft now stores project config files in a new `config/project/` folder, regardless of whether the (deprecated) `useProjectConfigFile` config setting is enabled, and syncing new project config file changes is now optional. +- The public GraphQL schema’s access settings are now stored in the project config. ([#6078](https://github.com/craftcms/cms/issues/6078)) - Entry draft forms no longer have a primary action, and the Ctrl/Command + S keyboard shortcut now forces a resave of the draft, rather than publishing it. ([#6199](https://github.com/craftcms/cms/issues/6199)) - When creating a new field, the “Use this field’s values as search keywords?” setting is now disabled by default. ([#6390](https://github.com/craftcms/cms/issues/6390)) - The `project-config/sync` command has been renamed to `project-config/apply`. @@ -96,7 +99,6 @@ - `craft\elements\db\ElementQuery::$enabledForSite` is now set to `false` by default, leaving it up to elements’ status conditions to factor in the site-specific element statuses. ([#6273](https://github.com/craftcms/cms/issues/6273)) - `craft\helpers\Component::createComponent()` now creates component objects via `Craft::createObject()`. ([#6097](https://github.com/craftcms/cms/issues/6097)) - `craft\services\ProjectConfig::CONFIG_FILENAME` is no longer deprecated. -- The GraphQL public schema token settings are now stored in the project config. ([#6078](https://github.com/craftcms/cms/issues/6078)) - The `_layouts/base` template now supports a `bodyAttributes` variable. - Updated Yii to 2.0.36. - Updated PrismJS to 1.20.0.