diff --git a/CHANGELOG.md b/CHANGELOG.md index 966641fa86..37f9d566c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -442,6 +442,8 @@ Updated Motoko to [0.10.2](https://github.com/dfinity/motoko/releases/tag/0.10.2 ### Frontend canister +Fixed an issue where the `allow_raw_access` configuration value was being affected by the order of declarations in the configuration file in `.ic-assets.json`. + Defining a custom `etag` header no longer breaks certification. Fixed a certification issue where under certain conditions the fallback file (`/index.html`) was served with an incomplete certificate tree, not proving sufficiently that the fallback file may be used as a replacement. diff --git a/e2e/tests-dfx/assetscanister.bash b/e2e/tests-dfx/assetscanister.bash index 56e6c3aca3..372919b8c3 100644 --- a/e2e/tests-dfx/assetscanister.bash +++ b/e2e/tests-dfx/assetscanister.bash @@ -1490,29 +1490,25 @@ CHERRIES" "$stdout" "match": "nevermatchme", "cache": { "max_age": 2000 - }, - "allow_raw_access": true + } }' assert_match 'WARN: 4 unmatched configurations in .*/src/e2e_project_frontend/assets/somedir/.ic-assets.json config file:' assert_contains 'WARN: { "match": "nevermatchme", "headers": {}, - "ignore": false, - "allow_raw_access": true + "ignore": false } WARN: { "match": "nevermatchmetoo", "headers": {}, - "ignore": false, - "allow_raw_access": true + "ignore": false } WARN: { "match": "non-matcher", "headers": { "x-header": "x-value" }, - "ignore": false, - "allow_raw_access": true + "ignore": false }' # splitting this up into two checks, because the order is different on macos vs ubuntu assert_contains 'WARN: { @@ -1520,8 +1516,7 @@ WARN: { "headers": { "x-header": "x-value" }, - "ignore": false, - "allow_raw_access": true + "ignore": false }' } diff --git a/src/canisters/frontend/ic-asset/src/asset/config.rs b/src/canisters/frontend/ic-asset/src/asset/config.rs index 68a883dde3..286a2ae05b 100644 --- a/src/canisters/frontend/ic-asset/src/asset/config.rs +++ b/src/canisters/frontend/ic-asset/src/asset/config.rs @@ -34,10 +34,6 @@ pub(crate) struct CacheConfig { pub(crate) max_age: Option, } -fn default_raw_access() -> Option { - Some(true) -} - /// A single configuration object, from `.ic-assets.json` config file #[derive(Derivative, Clone, Serialize)] #[derivative(Debug, PartialEq)] @@ -66,19 +62,14 @@ pub struct AssetConfigRule { allow_raw_access: Option, } -#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Default)] enum Maybe { Null, + #[default] Absent, Value(T), } -impl Default for Maybe { - fn default() -> Self { - Self::Absent - } -} - impl AssetConfigRule { fn applies(&self, canonical_path: &Path) -> bool { // TODO: better dot files/dirs handling, awaiting upstream changes: @@ -248,15 +239,12 @@ impl AssetConfig { (_, Maybe::Null) => self.headers = None, (_, Maybe::Absent) => (), }; - if other.ignore.is_some() { self.ignore = other.ignore; } - if other.enable_aliasing.is_some() { self.enable_aliasing = other.enable_aliasing; } - if other.allow_raw_access.is_some() { self.allow_raw_access = other.allow_raw_access; } @@ -269,6 +257,7 @@ impl AssetConfig { mod rule_utils { use super::{AssetConfig, AssetConfigRule, CacheConfig, HeadersConfig, Maybe}; use crate::error::LoadRuleError; + use derivative::Derivative; use globset::{Glob, GlobMatcher}; use serde::{Deserialize, Serializer}; use serde_json::Value; @@ -339,7 +328,7 @@ mod rule_utils { } } - #[derive(Deserialize)] + #[derive(Deserialize, Derivative)] #[serde(deny_unknown_fields)] pub(super) struct InterimAssetConfigRule { r#match: String, @@ -348,7 +337,7 @@ mod rule_utils { headers: Maybe, ignore: Option, enable_aliasing: Option, - #[serde(default = "super::default_raw_access")] + #[derivative(Default(value = "Some(true)"))] allow_raw_access: Option, } @@ -427,6 +416,16 @@ mod rule_utils { s.push_str(&format!(" - HTTP cache max-age: {}\n", max_age)); } } + if let Some(allow_raw_access) = self.allow_raw_access { + s.push_str(&format!( + " - enable raw access: {}\n", + if allow_raw_access { + "enabled" + } else { + "disabled" + } + )); + } if let Some(aliasing) = self.enable_aliasing { s.push_str(&format!( " - URL path aliasing: {}\n", @@ -933,7 +932,155 @@ mod with_tempdir { AssetConfig { allow_raw_access: Some(true), ..Default::default() - }, + } ); } + + #[test] + fn the_order_does_not_matter() { + let cfg = Some(HashMap::from([( + "".to_string(), + r#"[ + { + "match": "**/deep/**/*", + "allow_raw_access": false, + "cache": { + "max_age": 22 + }, + "enable_aliasing": true, + "ignore": true + }, + { + "match": "**/*", + "headers": { + "X-Frame-Options": "DENY" + } + } + ] +"# + .to_string(), + )])); + let cfg2 = Some(HashMap::from([( + "".to_string(), + r#"[ + { + "match": "**/*", + "headers": { + "X-Frame-Options": "DENY" + } + }, + { + "match": "**/deep/**/*", + "allow_raw_access": false, + "cache": { + "max_age": 22 + }, + "enable_aliasing": true, + "ignore": true + } + ] + "# + .to_string(), + )])); + + let x = { + let assets_temp_dir = create_temporary_assets_directory(cfg, 0); + let assets_dir = assets_temp_dir.path().canonicalize().unwrap(); + let mut assets_config = AssetSourceDirectoryConfiguration::load(&assets_dir).unwrap(); + assets_config + .get_asset_config(assets_dir.join("nested/deep/the-next-thing.toml").as_path()) + .unwrap() + }; + let y = { + let assets_temp_dir = create_temporary_assets_directory(cfg2, 0); + let assets_dir = assets_temp_dir.path().canonicalize().unwrap(); + let mut assets_config = AssetSourceDirectoryConfiguration::load(&assets_dir).unwrap(); + assets_config + .get_asset_config(assets_dir.join("nested/deep/the-next-thing.toml").as_path()) + .unwrap() + }; + + dbg!(&x, &y); + assert_eq!(x.allow_raw_access, Some(false)); + assert_eq!(y.allow_raw_access, Some(false)); + assert_eq!(x.enable_aliasing, Some(true)); + assert_eq!(y.enable_aliasing, Some(true)); + assert_eq!(x.ignore, Some(true)); + assert_eq!(y.ignore, Some(true)); + assert_eq!(x.cache.clone().unwrap().max_age, Some(22)); + assert_eq!(y.cache.clone().unwrap().max_age, Some(22)); + + // same as above but with different values + let cfg = Some(HashMap::from([( + "".to_string(), + r#"[ + { + "match": "**/deep/**/*", + "allow_raw_access": true, + "enable_aliasing": false, + "ignore": false, + "headers": { + "X-Frame-Options": "ALLOW" + } + }, + { + "match": "**/*", + "cache": { + "max_age": 22 + } + } + ] +"# + .to_string(), + )])); + let cfg2 = Some(HashMap::from([( + "".to_string(), + r#"[ + { + "match": "**/*", + "cache": { + "max_age": 22 + } + }, + { + "match": "**/deep/**/*", + "allow_raw_access": true, + "enable_aliasing": false, + "ignore": false, + "headers": { + "X-Frame-Options": "ALLOW" + } + } + ] + "# + .to_string(), + )])); + + let x = { + let assets_temp_dir = create_temporary_assets_directory(cfg, 0); + let assets_dir = assets_temp_dir.path().canonicalize().unwrap(); + let mut assets_config = AssetSourceDirectoryConfiguration::load(&assets_dir).unwrap(); + assets_config + .get_asset_config(assets_dir.join("nested/deep/the-next-thing.toml").as_path()) + .unwrap() + }; + let y = { + let assets_temp_dir = create_temporary_assets_directory(cfg2, 0); + let assets_dir = assets_temp_dir.path().canonicalize().unwrap(); + let mut assets_config = AssetSourceDirectoryConfiguration::load(&assets_dir).unwrap(); + assets_config + .get_asset_config(assets_dir.join("nested/deep/the-next-thing.toml").as_path()) + .unwrap() + }; + + dbg!(&x, &y); + assert_eq!(x.allow_raw_access, Some(true)); + assert_eq!(y.allow_raw_access, Some(true)); + assert_eq!(x.enable_aliasing, Some(false)); + assert_eq!(y.enable_aliasing, Some(false)); + assert_eq!(x.ignore, Some(false)); + assert_eq!(y.ignore, Some(false)); + assert_eq!(x.cache.clone().unwrap().max_age, Some(22)); + assert_eq!(y.cache.clone().unwrap().max_age, Some(22)); + } }