diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md new file mode 100644 index 00000000000..b121a03ba32 --- /dev/null +++ b/CHANGELOG-WIP.md @@ -0,0 +1,13 @@ +# Release Notes for Craft CMS 4.18 (WIP) + +### Development +- The `|default` Twig filter and `is empty` Twig test now treat all `yii\base\Model` instances as not empty. ([#18727](https://github.com/craftcms/cms/issues/18727)) +- Added `craft\filters\SecFetchSiteFilter` for request origin verification. ([#18641](https://github.com/craftcms/cms/pull/18641)) + +### Extensibility +- Removed `craft\controllers\AppController::actionResourceJs()`. ([#18559](https://github.com/craftcms/cms/pull/18559)) + +### System +- Cross-domain script tags added by JavaScript are now loaded directly, rather than via a proxy. ([#18559](https://github.com/craftcms/cms/pull/18559)) +- Updated the built-in composer.phar to 2.9.7. ([#18761](https://github.com/craftcms/cms/issues/18761)) +- Fixed a [moderate-severity](https://github.com/craftcms/cms/security/policy#severity--remediation) JavaScript injection vulnerability. (GHSA-c55v-343g-5xff) diff --git a/lib/composer.phar b/lib/composer.phar index 15c4a2081c9..ae06efc42e2 100755 Binary files a/lib/composer.phar and b/lib/composer.phar differ diff --git a/src/controllers/AppController.php b/src/controllers/AppController.php index 91fa4e45f1f..468fb378be6 100644 --- a/src/controllers/AppController.php +++ b/src/controllers/AppController.php @@ -22,9 +22,6 @@ use craft\helpers\DateTimeHelper; use craft\helpers\Html; use craft\helpers\Json; -use craft\helpers\Path; -use craft\helpers\Session; -use craft\helpers\StringHelper; use craft\helpers\Update as UpdateHelper; use craft\helpers\UrlHelper; use craft\models\Update; @@ -34,7 +31,6 @@ use craft\web\ServiceUnavailableHttpException; use DateInterval; use Throwable; -use yii\base\InvalidArgumentException; use yii\base\InvalidConfigException; use yii\web\BadRequestHttpException; use yii\web\Cookie; @@ -103,51 +99,6 @@ public function actionHealthCheck(): Response return $this->response; } - /** - * Loads the given JavaScript resource URL and returns it. - * - * @param string $url - * @return Response - */ - public function actionResourceJs(string $url): Response - { - $this->requireCpRequest(); - - $assetManager = Craft::$app->getAssetManager(); - $baseUrl = StringHelper::ensureRight($assetManager->baseUrl, '/'); - if (!str_starts_with($url, $baseUrl)) { - throw new BadRequestHttpException("$url does not appear to be a resource URL"); - } - - $resourceUri = preg_replace('/^(.*)\?.*/', '$1', substr($url, strlen($baseUrl))); - - if (!Path::ensurePathIsContained($resourceUri)) { - throw new BadRequestHttpException("Invalid resource: $resourceUri"); - } - - // If we aren’t caching source paths in the resourcepaths table, - // then we’re going to have to fetch the file over HTTP - if (!$assetManager->cacheSourcePaths) { - // Close the PHP session in case this takes a while - Session::close(); - - $response = Craft::createGuzzleClient()->get($url); - $this->response->setCacheHeaders(); - $this->response->getHeaders()->set('content-type', 'application/javascript'); - return $this->asRaw($response->getBody()); - } - - try { - $publishedPath = App::resourcePathByUri($resourceUri); - } catch (InvalidArgumentException $e) { - throw new BadRequestHttpException($e->getMessage(), previous: $e); - } - - return $this->response->sendFile($publishedPath, null, [ - 'inline' => true, - ]); - } - /** * Returns the latest Craftnet API headers. * diff --git a/src/filters/SecFetchSiteFilter.php b/src/filters/SecFetchSiteFilter.php new file mode 100644 index 00000000000..fde065fde27 --- /dev/null +++ b/src/filters/SecFetchSiteFilter.php @@ -0,0 +1,74 @@ +setDefaults(); + + $request = Craft::$app->getRequest(); + + if (in_array($request->getMethod(), $this->safeMethods, true)) { + return true; + } + + $secFetchSite = $request->getHeaders()->get($this->headerName); + + if ($secFetchSite === 'same-origin') { + return true; + } + + if ($secFetchSite === 'same-site' && $this->allowSameSite) { + return true; + } + + if ($this->originOnly) { + throw new BadRequestHttpException($this->errorMessage); + } + + return true; + } + + private function setDefaults(): void + { + $this->safeMethods = $this->safeMethods ?? Craft::$app->getRequest()->csrfTokenSafeMethods; + $this->errorMessage = $this->errorMessage ?? Craft::t('yii', 'Unable to verify your data submission.'); + } +} diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js index 9841c8e7b8e..7624d9e0313 100644 --- a/src/web/assets/cp/dist/cp.js +++ b/src/web/assets/cp/dist/cp.js @@ -1,3 +1,3 @@ /*! For license information please see cp.js.LICENSE.txt */ -(function(){var __webpack_modules__={6322:function(){Craft.Accordion=Garnish.Base.extend({$trigger:null,targetSelector:null,_$target:null,init:function(t){var e=this;this.$trigger=$(t),this.$trigger.data("accordion")&&(console.warn("Double-instantiating an accordion trigger on an element"),this.$trigger.data("accordion").destroy()),this.$trigger.data("accordion",this),this.targetSelector=this.$trigger.attr("aria-controls")?"#".concat(this.$trigger.attr("aria-controls")):null,this.targetSelector&&(this._$target=$(this.targetSelector)),this.addListener(this.$trigger,"click","onTriggerClick"),this.addListener(this.$trigger,"keypress",function(t){var i=t.keyCode;i!==Garnish.SPACE_KEY&&i!==Garnish.RETURN_KEY||(t.preventDefault(),e.onTriggerClick())})},onTriggerClick:function(){"true"===this.$trigger.attr("aria-expanded")?this.hideTarget(this._$target):this.showTarget(this._$target)},showTarget:function(t){var e=this;if(t&&t.length){this.showTarget._currentHeight=t.height(),t.removeClass("hidden"),this.$trigger.removeClass("collapsed").addClass("expanded").attr("aria-expanded","true");for(var i=0;i .address-card");for(var s=0;s=this.settings.maxItems)){var e=$(t).appendTo(this.$tbody),i=e.find(".delete");this.settings.sortable&&this.sorter.addItems(e),this.$deleteBtns=this.$deleteBtns.add(i),this.addListener(i,"click","handleDeleteBtnClick"),this.totalItems++,this.updateUI()}},reorderItems:function(){var t=this;if(this.settings.sortable){for(var e=[],i=0;i=this.settings.maxItems?$(this.settings.newItemBtnSelector).addClass("hidden"):$(this.settings.newItemBtnSelector).removeClass("hidden"))}},{defaults:{tableSelector:null,noItemsSelector:null,newItemBtnSelector:null,idAttribute:"data-id",nameAttribute:"data-name",sortable:!1,allowDeleteAll:!0,minItems:0,maxItems:null,reorderAction:null,deleteAction:null,reorderSuccessMessage:Craft.t("app","New order saved."),reorderFailMessage:Craft.t("app","Couldn’t save new order."),confirmDeleteMessage:Craft.t("app","Are you sure you want to delete “{name}”?"),deleteSuccessMessage:Craft.t("app","“{name}” deleted."),deleteFailMessage:Craft.t("app","Couldn’t delete “{name}”."),onReorderItems:$.noop,onDeleteItem:$.noop}})},258:function(){Craft.AssetImageEditor=Garnish.Modal.extend({$body:null,$footer:null,$imageTools:null,$buttons:null,$cancelBtn:null,$replaceBtn:null,$saveBtn:null,$focalPointBtn:null,$editorContainer:null,$straighten:null,$croppingCanvas:null,$spinner:null,$constraintContainer:null,$constraintRadioInputs:null,$customConstraints:null,canvas:null,image:null,viewport:null,focalPoint:null,grid:null,croppingCanvas:null,clipper:null,croppingRectangle:null,cropperHandles:null,cropperGrid:null,croppingShade:null,imageStraightenAngle:0,viewportRotation:0,originalWidth:0,originalHeight:0,imageVerticeCoords:null,zoomRatio:1,animationInProgress:!1,currentView:"",assetId:null,cacheBust:null,draggingCropper:!1,scalingCropper:!1,draggingFocal:!1,previousMouseX:0,previousMouseY:0,shiftKeyHeld:!1,editorHeight:0,editorWidth:0,cropperState:!1,scaleFactor:1,flipData:{},focalPointState:!1,maxImageSize:null,lastLoadedDimensions:null,imageIsLoading:!1,mouseMoveEvent:null,croppingConstraint:!1,constraintOrientation:"landscape",showingCustomConstraint:!1,saving:!1,renderImage:null,renderCropper:null,_queue:null,init:function(t,e){var i=this;this._queue=new Craft.Queue,this.cacheBust=Date.now(),this.setSettings(e,Craft.AssetImageEditor.defaults),null===this.settings.allowDegreeFractions&&(this.settings.allowDegreeFractions=Craft.isImagick),Garnish.prefersReducedMotion()&&(this.settings.animationDuration=1),this.assetId=t,this.flipData={x:0,y:0},this.$container=$('').appendTo(Garnish.$bod),this.$body=$('
').appendTo(this.$container),this.$footer=$('