Files
Gnuboard7/tests/scenarios/public-asset-cdn.yaml
T
HeuJung c341fe28a4 fix(core,admin_basic,basic): 공개 자산 CDN 교차 출처 요청의 토큰 노출·CORS 실패 수정 + S3 접속 필드 노출 조건 확장
공개 (S3 드라이버) × (공개 자산 CDN) 병합 결과를 실 AWS S3 로 교차 실측하다
발견한 결함과, 그 과정에서 드러난 설정 화면의 사각을 함께 처리한다.

## 교차 출처 공개 자산 요청

공개 자산 디스크를 켜면 첨부·이미지의 download_url 이 외부 origin 절대 URL 이 되는데,
API 클라이언트가 URL 출처를 보지 않고 Authorization 을 붙이고 있었다. 그 결과
① 세션 토큰이 제3자 origin 으로 전송되고(응답을 허용하는 CDN 에서는 접근 로그에 남는다)
② 안전목록 밖 헤더라 preflight 가 발생해 CORS 미설정 버킷에서는 이미지가 통째로 실패했다.
상점 화면은 <img> 라 무증상이고 관리자 화면에서만 발현해 발견이 늦었다.

- 코어: 동일 출처 요청에만 Authorization 첨부 (상대 경로는 항상 동일 출처로 간주)
- 템플릿 2종: 교차 출처 URL 은 인증 XHR 없이 그대로 사용 (썸네일·갤러리·다운로드 3지점씩)

로컬 public 디스크나 허용적 CORS 를 주는 오브젝트 스토리지에서는 재현되지 않는다 —
외부 origin 과 CORS 미제공이 겹쳐야 드러나므로 회귀 테스트는 두 조건을 모두 고정한다.

## S3 접속 필드 노출 조건

파일 스토리지는 로컬로 두고 공개 자산만 S3 로 보내는 구성에서 접속 정보를 넣을 화면이
없었다(도움말이 보이지 않는 필드를 가리켰다). 두 축 중 하나라도 s3 면 블록을 노출하고,
연결 테스트 게이트의 적용 대상도 같은 조건으로 넓힌다. 재검증 트리거는 S3 접속 설정
변경으로 한정한다 — 트리거까지 넓히면 "파일 스토리지=s3 에서 공개 자산만 변경" 하는
기존 흐름이 저장 불가가 된다.
2026-08-14 10:32:24 +09:00

90 lines
5.3 KiB
YAML

feature: 공개 자산 직접 URL(CDN) 서빙 (공개#100)
description: |
StorageInterface::url() 의 public 하드코딩을 완화하고 `core.storage.filter_url`
훅을 신설해, 완전 공개 자산(상품/카테고리/리뷰/에디터 이미지)의 직접 URL(CDN)
서빙을 옵트인으로 연다. 미설정 시 기존 PHP 스트리밍 100% 보존.
결정 사슬:
- url(): public 디스크 또는 filesystems.disks.{disk}.url 설정 디스크 → 직접 URL,
그 외 null. 결과는 디스크 무관 core.storage.filter_url 훅 상시 통과(공급/수정/차단).
- 디스크 결정: 확장 개별 설정(public_asset_disk) > 코어 전역
(drivers.public_asset_disk → core.storage.public_asset_disk) > 미설정(스트리밍).
'none'/고아 디스크는 스트리밍 폴백.
- 소비처: 행 disk 로 url() 시도 → null 이면 기존 API 서빙 경로 폴백.
서빙/삭제/이동은 행 disk 기준(혼재 운용).
- 권한 검사가 걸린 첨부(board/page/코어)는 배선 자체가 없음 — 직접 URL 이
권한을 우회하므로 스트리밍 유지가 보안 전제.
coverage_strategy: pairwise
axes:
disk_setting: [none, public, fake_cdn, s3_without_url]
consumer: [product, category, review, editor]
row_state: [legacy_local_row, new_remote_row]
override: [follow_core, module_override, none_override]
hook: [unregistered, supply, block]
e2e: [drivers_tab_card, ecommerce_settings_field, save_roundtrip]
effects:
- url_null_for_disk_without_url_config
- url_returned_for_public_and_url_configured_disk
- blank_url_config_defended
- filter_url_hook_always_fires_with_six_context_keys
- hook_supply_modify_block_respected
- download_url_falls_back_to_api_path_when_direct_unavailable
- mixed_rows_resolve_per_row_disk
- serve_and_delete_follow_row_disk
- orphan_disk_falls_back_to_streaming
- none_override_forces_streaming_over_global
- settings_catalog_includes_plugin_registered_disks
- invalid_disk_rejected_with_422
- board_response_strings_unchanged
- settings_load_recursion_free
- global_setting_injects_core_config_with_none_normalized
- seo_guard_passes_absolute_cdn_url_unmodified
- cross_origin_asset_request_omits_session_token
- s3_credentials_visible_when_public_asset_disk_is_s3
test_files:
- tests/Unit/Extension/Storage/CoreStorageDriverTest.php
- tests/Unit/Extension/Storage/ModuleStorageDriverTest.php
- tests/Unit/Extension/Storage/PluginStorageDriverTest.php
- tests/Unit/Extension/Storage/StorageCategoryDiskTest.php
- tests/Unit/Services/DriverRegistryServiceTest.php
- tests/Unit/Services/DriverExtensionIntegrationTest.php
- tests/Unit/Models/AttachmentUrlTest.php
- tests/Unit/Seo/ComponentHtmlMapperTest.php
- tests/Feature/Api/Admin/SettingsControllerDriverOptionsTest.php
- tests/Unit/Providers/SettingsServiceProviderPublicAssetDiskTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/Models/HasDirectAssetUrlTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/ProductImageServiceRowDiskTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/CategoryImageServiceRowDiskTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/ProductReviewImageServiceRowDiskTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/StorageDiskRecursionTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/ProductServiceDetailImageUrlTest.php
- modules/_bundled/sirsoft-board/tests/Unit/AttachmentPreviewUrlTest.php
- modules/_bundled/sirsoft-board/tests/Unit/Resources/PostResourceThumbnailUrlTest.php
- modules/_bundled/sirsoft-board/tests/Unit/Repositories/BoardRepositoryPopularAvatarTest.php
- plugins/_bundled/sirsoft-ckeditor5/tests/Unit/Ckeditor5ImageUploadUrlTest.php
- plugins/_bundled/sirsoft-ckeditor5/tests/Unit/ImageServeServiceRowDiskTest.php
- plugins/_bundled/sirsoft-ckeditor5/tests/Unit/StorageDiskRecursionTest.php
- plugins/_bundled/sirsoft-ckeditor5/tests/Unit/PluginSettingsCatalogAttachTest.php
- modules/_bundled/sirsoft-ecommerce/tests/Unit/Http/Requests/StoreEcommerceSettingsRequestPublicAssetDiskTest.php
- plugins/_bundled/sirsoft-ckeditor5/tests/Unit/PluginSettingsPublicAssetDiskValidationTest.php
- templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/public-asset-storage.spec.ts
- modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/public-asset-disk-override.spec.ts
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-public-asset-storage.test.tsx
- modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/adminEcommerceSettingsPublicAssetDisk.test.tsx
- plugins/_bundled/sirsoft-ckeditor5/resources/js/__tests__/layouts/pluginSettingsPublicAssetDisk.test.tsx
- resources/js/core/api/__tests__/ApiClient.test.ts
- templates/_bundled/sirsoft-admin_basic/__tests__/components/assetOrigin.crossOriginAuth.test.tsx
- templates/_bundled/sirsoft-basic/__tests__/components/assetOrigin.crossOriginAuth.test.tsx
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-drivers-s3-visibility.test.tsx
rules_layer_coverage:
- rule: no-storage-disk-direct
coverage: 소비처는 StorageInterface 경유 — Storage facade 직접 호출 도입 없음
- rule: repository-interface-injection
coverage: 서비스 시그니처 무변경 — contextual binding 만 카테고리 디스크로 교체