Skip to content

Commit

Permalink
Merge branch '4.x' into 4.13
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonkelly committed Oct 15, 2024
2 parents 023ba22 + d5be48e commit d2bb088
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release Notes for Craft CMS 4

## Unreleased

- Fixed a privilege escalation vulnerability.

## 4.12.6.1 - 2024-10-09

- Custom field condition rules are now ignored if they reference a field with an incompatible type. ([#15850](https://github.com/craftcms/cms/issues/15850))
Expand Down
17 changes: 11 additions & 6 deletions src/controllers/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use craft\helpers\UrlHelper;
use craft\models\Update;
use craft\models\Updates;
use craft\utilities\Updates as UpdatesUtility;
use craft\web\Controller;
use craft\web\ServiceUnavailableHttpException;
use DateInterval;
Expand Down Expand Up @@ -147,9 +148,11 @@ public function actionCheckForUpdates(): Response
{
$this->requireAcceptsJson();

// Require either the 'performUpdates' or 'utility:updates' permission
$userSession = Craft::$app->getUser();
if (!$userSession->checkPermission('performUpdates') && !$userSession->checkPermission('utility:updates')) {
// Require either the 'performUpdates' permission or access to the Updates utility
if (
!Craft::$app->getUser()->checkPermission('performUpdates') &&
!Craft::$app->getUtilities()->checkAuthorization(UpdatesUtility::class)
) {
throw new ForbiddenHttpException('User is not permitted to perform this action');
}

Expand Down Expand Up @@ -177,9 +180,11 @@ public function actionCacheUpdates(): Response
{
$this->requireAcceptsJson();

// Require either the 'performUpdates' or 'utility:updates' permission
$userSession = Craft::$app->getUser();
if (!$userSession->checkPermission('performUpdates') && !$userSession->checkPermission('utility:updates')) {
// Require either the 'performUpdates' permission or access to the Updates utility
if (
!Craft::$app->getUser()->checkPermission('performUpdates') &&
!Craft::$app->getUtilities()->checkAuthorization(UpdatesUtility::class)
) {
throw new ForbiddenHttpException('User is not permitted to perform this action');
}

Expand Down
6 changes: 5 additions & 1 deletion src/controllers/AssetIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use craft\web\Controller;
use Throwable;
use yii\web\BadRequestHttpException;
use yii\web\ForbiddenHttpException;
use yii\web\Response;

/** @noinspection ClassOverridesFieldOfSuperClassInspection */
Expand All @@ -41,7 +42,10 @@ public function beforeAction($action): bool
}

// No permission no bueno
$this->requirePermission('utility:asset-indexes');
if (!Craft::$app->getUtilities()->checkAuthorization(AssetIndexes::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$this->requireAcceptsJson();

return true;
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/ProjectConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use craft\helpers\FileHelper;
use craft\helpers\ProjectConfig;
use craft\helpers\StringHelper;
use craft\utilities\ProjectConfig as ProjectConfigUtility;
use craft\web\Controller;
use Symfony\Component\Yaml\Yaml;
use yii\base\Exception;
Expand All @@ -35,7 +36,10 @@ public function beforeAction($action): bool
return false;
}

$this->requirePermission('utility:project-config');
if (!Craft::$app->getUtilities()->checkAuthorization(ProjectConfigUtility::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

return true;
}

Expand Down
26 changes: 21 additions & 5 deletions src/controllers/QueueController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use craft\helpers\App;
use craft\helpers\Json;
use craft\queue\QueueInterface;
use craft\utilities\QueueManager;
use craft\web\Controller;
use yii\base\InvalidArgumentException;
use yii\db\Exception as YiiDbException;
Expand Down Expand Up @@ -95,7 +96,10 @@ public function actionRetry(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$id = $this->request->getRequiredBodyParam('id');
$this->queue->retry($id);
Expand All @@ -113,7 +117,10 @@ public function actionRelease(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$id = $this->request->getRequiredBodyParam('id');
$this->queue->release($id);
Expand All @@ -134,7 +141,10 @@ public function actionReleaseAll(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$this->queue->releaseAll();

Expand All @@ -153,7 +163,10 @@ public function actionRetryAll(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$this->queue->retryAll();

Expand Down Expand Up @@ -189,7 +202,10 @@ public function actionGetJobInfo(): Response
public function actionGetJobDetails(): Response
{
$this->requireAcceptsJson();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$jobId = $this->request->getRequiredParam('id');
$details = [
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/SystemMessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

use Craft;
use craft\models\SystemMessage;
use craft\utilities\SystemMessages;
use craft\web\Controller;
use yii\web\ForbiddenHttpException;
use yii\web\Response;

/**
Expand All @@ -34,7 +36,9 @@ public function beforeAction($action): bool
Craft::$app->requireEdition(Craft::Pro);

// Make sure they have access to the System Messages utility
$this->requirePermission('utility:system-messages');
if (!Craft::$app->getUtilities()->checkAuthorization(SystemMessages::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

return true;
}
Expand Down
39 changes: 31 additions & 8 deletions src/controllers/UtilitiesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
use craft\helpers\Queue;
use craft\queue\jobs\FindAndReplace;
use craft\utilities\ClearCaches;
use craft\utilities\DbBackup;
use craft\utilities\DeprecationErrors;
use craft\utilities\FindAndReplace as FindAndReplaceUtility;
use craft\utilities\Migrations;
use craft\utilities\Updates;
use craft\utilities\Upgrade;
use craft\web\assets\utilities\UtilitiesAsset;
Expand Down Expand Up @@ -100,10 +104,13 @@ public function actionShowUtility(string $id): Response
*/
public function actionGetDeprecationErrorTracesModal(): Response
{
$this->requirePermission('utility:deprecation-errors');
$this->requirePostRequest();
$this->requireAcceptsJson();

if (!Craft::$app->getUtilities()->checkAuthorization(DeprecationErrors::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$logId = Craft::$app->request->getRequiredParam('logId');
$html = $this->getView()->renderTemplate('_components/utilities/DeprecationErrors/traces_modal.twig', [
'log' => Craft::$app->deprecator->getLogById($logId),
Expand All @@ -122,10 +129,13 @@ public function actionGetDeprecationErrorTracesModal(): Response
*/
public function actionDeleteAllDeprecationErrors(): Response
{
$this->requirePermission('utility:deprecation-errors');
$this->requirePostRequest();
$this->requireAcceptsJson();

if (!Craft::$app->getUtilities()->checkAuthorization(DeprecationErrors::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

Craft::$app->deprecator->deleteAllLogs();

return $this->asSuccess();
Expand All @@ -139,10 +149,13 @@ public function actionDeleteAllDeprecationErrors(): Response
*/
public function actionDeleteDeprecationError(): Response
{
$this->requirePermission('utility:deprecation-errors');
$this->requirePostRequest();
$this->requireAcceptsJson();

if (!Craft::$app->getUtilities()->checkAuthorization(DeprecationErrors::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$logId = $this->request->getRequiredBodyParam('logId');
Craft::$app->deprecator->deleteLogById($logId);

Expand All @@ -158,7 +171,9 @@ public function actionDeleteDeprecationError(): Response
*/
public function actionClearCachesPerformAction(): Response
{
$this->requirePermission('utility:clear-caches');
if (!Craft::$app->getUtilities()->checkAuthorization(ClearCaches::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$caches = $this->request->getRequiredBodyParam('caches');

Expand Down Expand Up @@ -197,7 +212,9 @@ public function actionClearCachesPerformAction(): Response
*/
public function actionInvalidateTags(): Response
{
$this->requirePermission('utility:clear-caches');
if (!Craft::$app->getUtilities()->checkAuthorization(ClearCaches::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$tags = $this->request->getRequiredBodyParam('tags');
$cache = Craft::$app->getCache();
Expand All @@ -218,7 +235,9 @@ public function actionInvalidateTags(): Response
*/
public function actionDbBackupPerformAction(): ?Response
{
$this->requirePermission('utility:db-backup');
if (!Craft::$app->getUtilities()->checkAuthorization(DbBackup::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

try {
$backupPath = Craft::$app->getDb()->backup();
Expand Down Expand Up @@ -251,7 +270,9 @@ public function actionDbBackupPerformAction(): ?Response
*/
public function actionFindAndReplacePerformAction(): Response
{
$this->requirePermission('utility:find-replace');
if (!Craft::$app->getUtilities()->checkAuthorization(FindAndReplaceUtility::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$params = $this->request->getRequiredBodyParam('params');

Expand All @@ -273,7 +294,9 @@ public function actionFindAndReplacePerformAction(): Response
*/
public function actionApplyNewMigrations(): Response
{
$this->requirePermission('utility:migrations');
if (!Craft::$app->getUtilities()->checkAuthorization(Migrations::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$migrator = Craft::$app->getContentMigrator();

Expand Down
8 changes: 6 additions & 2 deletions src/helpers/Cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use craft\models\FieldLayout;
use craft\models\FieldLayoutTab;
use craft\models\Site;
use craft\utilities\ProjectConfig as ProjectConfigUtility;
use craft\utilities\Updates;
use craft\web\twig\TemplateLoaderException;
use craft\web\View;
use yii\base\Event;
Expand Down Expand Up @@ -127,10 +129,12 @@ public static function alerts(?string $path = null, bool $fetch = false): array
]);
}

$utilitiesService = Craft::$app->getUtilities();

// Critical update available?
if (
$path !== 'utilities/updates' &&
$user->can('utility:updates') &&
$utilitiesService->checkAuthorization(Updates::class) &&
Craft::$app->getUpdates()->getIsCriticalUpdateAvailable()
) {
$alerts[] = Craft::t('app', 'A critical update is available.') .
Expand All @@ -141,7 +145,7 @@ public static function alerts(?string $path = null, bool $fetch = false): array
$projectConfig = Craft::$app->getProjectConfig();
if (
$path !== 'utilities/project-config' &&
$user->can('utility:project-config') &&
$utilitiesService->checkAuthorization(ProjectConfigUtility::class) &&
$projectConfig->areChangesPending() &&
($projectConfig->writeYamlAutomatically || $projectConfig->get('dateModified') <= $projectConfig->get('dateModified', true))
) {
Expand Down
17 changes: 15 additions & 2 deletions src/services/Utilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,22 @@ public function checkAuthorization(string $class): bool
/** @var string|UtilityInterface $class */
/** @phpstan-var class-string<UtilityInterface>|UtilityInterface $class */
$utilityId = $class::id();
$user = Craft::$app->getUser();

return $utilityId === ProjectConfigUtility::id() ? $user->getIsAdmin() : $user->checkPermission('utility:' . $utilityId);
// The Project Config utility is for admins only!
if ($class === ProjectConfigUtility::class) {
if (!Craft::$app->getUser()->getIsAdmin()) {
return false;
}
} elseif (!Craft::$app->getUser()->checkPermission("utility:$utilityId")) {
return false;
}

// Make sure the utility isn't disabled
if (in_array($utilityId, Craft::$app->getConfig()->getGeneral()->disabledUtilities)) {
return false;
}

return true;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/web/assets/cp/CpAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use craft\i18n\Locale;
use craft\models\Section;
use craft\services\Sites;
use craft\utilities\QueueManager;
use craft\web\AssetBundle;
use craft\web\assets\axios\AxiosAsset;
use craft\web\assets\d3\D3Asset;
Expand Down Expand Up @@ -477,7 +478,7 @@ private function _craftData(): array
'apiParams' => Craft::$app->apiParams,
'appId' => Craft::$app->id,
'autosaveDrafts' => $generalConfig->autosaveDrafts,
'canAccessQueueManager' => $userSession->checkPermission('utility:queue-manager'),
'canAccessQueueManager' => Craft::$app->getUtilities()->checkAuthorization(QueueManager::class),
'dataAttributes' => Html::$dataAttributes,
'defaultIndexCriteria' => [],
'editableCategoryGroups' => $upToDate ? $this->_editableCategoryGroups() : [],
Expand Down

0 comments on commit d2bb088

Please sign in to comment.