diff --git a/dsc/tests/dsc_adaptedResource.tests.ps1 b/dsc/tests/dsc_adaptedResource.tests.ps1 new file mode 100644 index 000000000..681665715 --- /dev/null +++ b/dsc/tests/dsc_adaptedResource.tests.ps1 @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Tests for adapted resources' { + BeforeAll { + $isAdmin = if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + [System.Security.Principal.WindowsPrincipal]::new($identity).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } + else { + [System.Environment]::UserName -eq 'root' + } + } + + It 'Security context for operation works' -TestCases @( + @{ securityContext = 'Elevated'; operation = 'get' }, + @{ securityContext = 'Elevated'; operation = 'test' }, + @{ securityContext = 'Elevated'; operation = 'set' }, + @{ securityContext = 'Elevated'; operation = 'delete' }, + @{ securityContext = 'Elevated'; operation = 'export' }, + @{ securityContext = 'Restricted'; operation = 'get' }, + @{ securityContext = 'Restricted'; operation = 'test' }, + @{ securityContext = 'Restricted'; operation = 'set' }, + @{ securityContext = 'Restricted'; operation = 'delete' }, + @{ securityContext = 'Restricted'; operation = 'export' }, + @{ securityContext = 'Current'; operation = 'get' }, + @{ securityContext = 'Current'; operation = 'test' }, + @{ securityContext = 'Current'; operation = 'set' }, + @{ securityContext = 'Current'; operation = 'delete' }, + @{ securityContext = 'Current'; operation = 'export' } + ) { + param($securityContext, $operation) + + $resourceType = "Adapted/SecurityContext$securityContext" + $json = @{ + one = $securityContext + } | ConvertTo-Json -Compress + $out = dsc -l trace resource $operation -r $resourceType 2>$TestDrive/error.txt -i $json + $errorTxt = Get-Content -Path $TestDrive/error.txt -Raw + if ($securityContext -eq 'Elevated' -and !$isAdmin) { + $LASTEXITCODE | Should -Be 2 -Because $errorTxt + $errorTxt | Should -BeLike "*Operation '$operation' for resource 'Adapted/SecurityContext$securityContext' requires security context 'elevated'*" + } + elseif ($securityContext -eq 'Restricted' -and $isAdmin) { + $LASTEXITCODE | Should -Be 2 + $errorTxt | Should -BeLike "*Operation '$operation' for resource 'Adapted/SecurityContext$securityContext' requires security context 'restricted'*" + } + else { + $LASTEXITCODE | Should -Be 0 + if ($operation -ne 'delete') { + $out | Should -Not -BeNullOrEmpty + } else { + $out | Should -BeNullOrEmpty + } + } + } +} diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 9a7538e09..3dd8f872f 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -902,6 +902,7 @@ pub fn load_adapted_resource_manifest(path: &Path, manifest: &AdaptedDscResource resource.require_adapter = Some(manifest.require_adapter.clone()); resource.directory = directory.to_path_buf(); resource.manifest = None; + resource.adapted_manifest = Some(manifest.clone()); resource.schema = Some(manifest.schema.clone()); Ok(resource) diff --git a/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs b/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs index 0e2dac392..99eaa7322 100644 --- a/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/adapted_resource_manifest.rs @@ -1,9 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{dscresources::{ - dscresource::Capability, resource_manifest::{Kind, ResourceManifest} -}, types::ResourceVersion}; +use crate::{ + configure::config_doc::SecurityContextKind, + dscresources::{ + dscresource::Capability, resource_manifest::{Kind, ResourceManifest} + }, + types::ResourceVersion}; use crate::{ schemas::dsc_repo::DscRepoSchema, types::FullyQualifiedTypeName, @@ -21,6 +24,42 @@ pub enum AdaptedPathOrContent { Content(Map), } +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, DscRepoSchema)] +#[dsc_repo_schema(base_name = "manifest.get", folder_path = "resource/adapted")] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetOperation { + pub require_security_context: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, DscRepoSchema)] +#[dsc_repo_schema(base_name = "manifest.set", folder_path = "resource/adapted")] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct SetOperation { + pub require_security_context: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, DscRepoSchema)] +#[dsc_repo_schema(base_name = "manifest.delete", folder_path = "resource/adapted")] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct DeleteOperation { + pub require_security_context: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, DscRepoSchema)] +#[dsc_repo_schema(base_name = "manifest.test", folder_path = "resource/adapted")] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct TestOperation { + pub require_security_context: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, DscRepoSchema)] +#[dsc_repo_schema(base_name = "manifest.export", folder_path = "resource/adapted")] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct ExportOperation { + pub require_security_context: Option, +} + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, DscRepoSchema)] #[serde(deny_unknown_fields, rename_all = "camelCase")] #[dsc_repo_schema( @@ -47,6 +86,16 @@ pub struct AdaptedDscResourceManifest { pub version: ResourceVersion, /// The capabilities of the resource. pub capabilities: Vec, + /// Properties of the Get operation for the resource. + pub get: Option, + /// Properties of the Set operation for the resource. + pub set: Option, + /// Properties of the Delete operation for the resource. + pub delete: Option, + /// Properties of the Test operation for the resource. + pub test: Option, + /// Properties of the Export operation for the resource. + pub export: Option, /// An optional condition for the resource to be active. pub condition: Option, /// An optional message indicating the resource is deprecated. If provided, the message will be shown when the resource is used. diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index c8b4dca7e..8c6325a89 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -8,7 +8,7 @@ use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; use std::{collections::HashMap, env, path::Path, process::Stdio}; -use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schema, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; +use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{RESOURCE_SCHEMAS, get_resource_schema}}, dscresources::{dscresource::Operation, resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}}, types::ExitCodesMap, util::canonicalize_which}; use crate::dscerror::DscError; use crate::locked_insert; use super::{ @@ -31,8 +31,9 @@ pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; /// /// # Arguments /// -/// * `resource` - The resource manifest +/// * `resource` - The resource /// * `filter` - The filter to apply to the resource in JSON +/// * `target_resource` - The target resource, if applicable /// /// # Errors /// @@ -50,7 +51,7 @@ pub fn invoke_get(resource: &DscResource, filter: &str, target_resource: Option< Some(target) => target, None => resource }; - validate_security_context(&get.require_security_context, &command_resource.type_name, "get")?; + validate_security_context(target_resource, &get.require_security_context, &command_resource.type_name, &Operation::Get)?; let args = process_get_args(get.args.as_ref(), filter, command_resource); if !filter.is_empty() { verify_json_from_manifest(resource, filter, target_resource)?; @@ -86,9 +87,11 @@ pub fn invoke_get(resource: &DscResource, filter: &str, target_resource: Option< /// /// # Arguments /// -/// * `resource` - The resource manifest +/// * `resource` - The resource /// * `desired` - The desired state of the resource in JSON /// * `skip_test` - If true, skip the test and directly invoke the set operation +/// * `execution_type` - Whether this is an actual set or what-if +/// * `target_resource` - The target resource, if applicable /// /// # Errors /// @@ -137,7 +140,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut let Some(set) = set_method.as_ref() else { return Err(DscError::NotImplemented("set".to_string())); }; - validate_security_context(&set.require_security_context, &command_resource.type_name, "set")?; + validate_security_context(target_resource, &set.require_security_context, &command_resource.type_name, &Operation::Set)?; verify_json_from_manifest(resource, desired, target_resource)?; // if resource doesn't implement a pre-test, we execute test first to see if a set is needed @@ -182,7 +185,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut Some(r) => r, None => resource, }; - validate_security_context(&get.require_security_context, &command_resource.type_name, "get")?; + validate_security_context(target_resource, &get.require_security_context, &command_resource.type_name, &Operation::Get)?; let args = process_get_args(get.args.as_ref(), desired, command_resource); let command_input = get_command_input(get.input.as_ref(), desired)?; @@ -317,6 +320,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut /// /// * `resource` - The resource manifest for the command resource. /// * `expected` - The expected state of the resource in JSON. +/// * `target_resource` - The target resource, if applicable. /// /// # Errors /// @@ -337,7 +341,7 @@ pub fn invoke_test(resource: &DscResource, expected: &str, target_resource: Opti Some(r) => r, None => resource, }; - validate_security_context(&test.require_security_context, &command_resource.type_name, "test")?; + validate_security_context(target_resource, &test.require_security_context, &command_resource.type_name, &Operation::Test)?; let args = process_get_args(test.args.as_ref(), expected, command_resource); let command_input = get_command_input(test.input.as_ref(), expected)?; @@ -487,6 +491,7 @@ fn invoke_synthetic_test(resource: &DscResource, expected: &str, target_resource /// * `resource` - The resource manifest for the command resource. /// * `cwd` - The current working directory. /// * `filter` - The filter to apply to the resource in JSON. +/// * `target_resource` - The target resource, if applicable. /// * `execution_type` - Whether this is an actual delete or what-if. /// /// # Errors @@ -506,7 +511,7 @@ pub fn invoke_delete(resource: &DscResource, filter: &str, target_resource: Opti Some(r) => r, None => resource, }; - validate_security_context(&delete.require_security_context, &command_resource.type_name, "delete")?; + validate_security_context(target_resource, &delete.require_security_context, &command_resource.type_name, &Operation::Delete)?; let (args, supports_whatif) = process_set_delete_args(delete.args.as_ref(), filter, command_resource, execution_type); if execution_type == &ExecutionKind::WhatIf && !supports_whatif { // perform a synthetic what-if by calling test and wrapping the TestResult in DeleteResultKind::SyntheticWhatIf @@ -533,6 +538,7 @@ pub fn invoke_delete(resource: &DscResource, filter: &str, target_resource: Opti /// * `resource` - The resource manifest for the command resource. /// * `cwd` - The current working directory. /// * `config` - The configuration to validate in JSON. +/// * `target_resource` - The target resource, if applicable. /// /// # Returns /// @@ -569,6 +575,7 @@ pub fn invoke_validate(resource: &DscResource, config: &str, target_resource: Op /// # Arguments /// /// * `resource` - The resource manifest +/// * `target_resource` - The target resource, if applicable /// /// # Errors /// @@ -679,7 +686,8 @@ fn verify_with_export_schema(input: &str, resource: &DscResource, target_resourc /// * `resource` - The resource manifest /// * `cwd` - The current working directory /// * `input` - Input to the command -/// +/// * `target_resource` - The target resource, if applicable +/// /// # Returns /// /// * `ExportResult` - The result of the export operation @@ -721,7 +729,7 @@ pub fn invoke_export(resource: &DscResource, input: Option<&str>, target_resourc Some(r) => r, None => resource, }; - validate_security_context(&export.require_security_context, &command_resource.type_name, "export")?; + validate_security_context(target_resource, &export.require_security_context, &command_resource.type_name, &Operation::Export)?; if let Some(input) = input { if !input.is_empty() { @@ -1326,7 +1334,54 @@ pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str "" } -fn validate_security_context(required_security_context: &Option, resource_type: &str, operation: &str) -> Result<(), DscError> { +fn validate_security_context(target_resource: Option<&DscResource>, required_security_context: &Option, resource_type: &str, operation: &Operation) -> Result<(), DscError> { + if let Some(resource) = target_resource && let Some(adapted_manifest) = &resource.adapted_manifest { + let require_security_context = match operation { + Operation::Get => { + if let Some(get) = &adapted_manifest.get { + &get.require_security_context + } else { + // if adapted manifest does not have get, fall back to original manifest + &None + } + }, + Operation::Set => { + if let Some(set) = &adapted_manifest.set { + &set.require_security_context + } else { + // if adapted manifest does not have get, fall back to original manifest + &None + } + }, + Operation::Delete => { + if let Some(delete) = &adapted_manifest.delete { + &delete.require_security_context + } else { + // if adapted manifest does not have get, fall back to original manifest + &None + } + }, + Operation::Test => { + if let Some(test) = &adapted_manifest.test { + &test.require_security_context + } else { + // if adapted manifest does not have get, fall back to original manifest + &None + } + }, + Operation::Export => { + if let Some(export) = &adapted_manifest.export { + &export.require_security_context + } else { + // if adapted manifest does not have get, fall back to original manifest + &None + } + }, + }; + if require_security_context.is_some() { + return validate_security_context(None, require_security_context, &resource.type_name, operation); + } + } match required_security_context { Some(SecurityContextKind::Elevated) => { if get_security_context() != SecurityContext::Admin { diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index d4053e494..b01cccb33 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::get_resource_schema}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}}; +use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::get_resource_schema}, dscresources::{adapted_resource_manifest::AdaptedDscResourceManifest, resource_manifest::{AdapterInputKind, Kind}}, types::{FullyQualifiedTypeName, ResourceVersion}}; use crate::discovery::discovery_trait::DiscoveryFilter; use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse}; use crate::schemas::transforms::idiomaticize_string_enum; @@ -12,6 +12,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use std::collections::{HashMap, HashSet}; +use std::fmt::Display; use std::path::PathBuf; use tracing::{debug, info, trace, warn}; @@ -61,10 +62,32 @@ pub struct DscResource { pub target_resource: Option>, /// The manifest of the resource. pub manifest: Option, + /// The adapted manifest of the resource, if available. + pub adapted_manifest: Option, /// The content of the adapted resource, if available. pub adapted_content: Option>, } +pub(crate) enum Operation { + Get, + Set, + Test, + Delete, + Export, +} + +impl Display for Operation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Operation::Get => write!(f, "get"), + Operation::Set => write!(f, "set"), + Operation::Test => write!(f, "test"), + Operation::Delete => write!(f, "delete"), + Operation::Export => write!(f, "export"), + } + } +} + #[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema, Ord, PartialOrd)] #[serde(rename_all = "camelCase")] #[schemars(transform = idiomaticize_string_enum)] @@ -118,6 +141,7 @@ impl DscResource { schema: None, target_resource: None, manifest: None, + adapted_manifest: None, adapted_content: None, } } diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index fee50512e..4d3ad8cc0 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -76,6 +76,169 @@ } } } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "Adapted/SecurityContextElevated", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "delete", + "test", + "export" + ], + "description": "An adapted resource for testing.", + "author": "DSC Team", + "requireAdapter": "Test/Adapter", + "path": "adaptedTest.dsc.adaptedResource.json", + "get": { + "requireSecurityContext": "elevated" + }, + "set": { + "requireSecurityContext": "elevated" + }, + "test": { + "requireSecurityContext": "elevated" + }, + "delete": { + "requireSecurityContext": "elevated" + }, + "export": { + "requireSecurityContext": "elevated" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "Adapted/SecurityContextElevated", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "one": { + "type": "string", + "title": "Property One", + "description": "This is property one of the adapted resource." + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the adapted resource instance." + } + } + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "Adapted/SecurityContextRestricted", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "test", + "export" + ], + "description": "An adapted resource for testing.", + "author": "DSC Team", + "requireAdapter": "Test/Adapter", + "path": "adaptedTest.dsc.adaptedResource.json", + "get": { + "requireSecurityContext": "restricted" + }, + "set": { + "requireSecurityContext": "restricted" + }, + "test": { + "requireSecurityContext": "restricted" + }, + "delete": { + "requireSecurityContext": "restricted" + }, + "export": { + "requireSecurityContext": "restricted" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "Adapted/SecurityContextRestricted", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "two": { + "type": "string", + "title": "Property Two", + "description": "This is property two of the adapted resource." + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the adapted resource instance." + } + } + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/adapted/manifest.json", + "type": "Adapted/SecurityContextCurrent", + "kind": "resource", + "version": "1.0.0", + "capabilities": [ + "get", + "set", + "test", + "export" + ], + "description": "An adapted resource for testing.", + "author": "DSC Team", + "requireAdapter": "Test/Adapter", + "path": "adaptedTest.dsc.adaptedResource.json", + "get": { + "requireSecurityContext": "current" + }, + "set": { + "requireSecurityContext": "current" + }, + "test": { + "requireSecurityContext": "current" + }, + "delete": { + "requireSecurityContext": "current" + }, + "export": { + "requireSecurityContext": "current" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/powershell/dsc", + "title": "Adapted/SecurityContextCurrent", + "description": "An adapted resource for testing.", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "two": { + "type": "string", + "title": "Property Two", + "description": "This is property two of the adapted resource." + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the adapted resource instance." + } + } + } + } } ], "resources": [ @@ -1100,6 +1263,29 @@ ] }, + "delete": { + "executable": "dsctest", + "args": [ + "adapter", + "--operation", + "delete", + { + "jsonInputArg": "--input", + "mandatory": true + }, + { + "resourceTypeArg": "--resource-type" + }, + { + "resourcePathArg": "--resource-path", + "includeQuotes": true + }, + { + "resourceVersionArg": "--resource-version" + } + + ] + }, "test": { "executable": "dsctest", "args": [ diff --git a/tools/dsctest/src/adapter.rs b/tools/dsctest/src/adapter.rs index c812061df..74b3aebeb 100644 --- a/tools/dsctest/src/adapter.rs +++ b/tools/dsctest/src/adapter.rs @@ -125,6 +125,30 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res }; Ok(serde_json::to_string(&adapted_deprecated).unwrap()) }, + "Adapted/SecurityContextElevated" => { + let adapted_security_context_elevated = AdaptedOne { + one: "elevated".to_string(), + name: None, + path: resource_path.clone(), + }; + Ok(serde_json::to_string(&adapted_security_context_elevated).unwrap()) + }, + "Adapted/SecurityContextRestricted" => { + let adapted_security_context_restricted = AdaptedOne { + one: "restricted".to_string(), + name: None, + path: resource_path.clone(), + }; + Ok(serde_json::to_string(&adapted_security_context_restricted).unwrap()) + }, + "Adapted/SecurityContextCurrent" => { + let adapted_security_context_current = AdaptedOne { + one: "current".to_string(), + name: None, + path: resource_path.clone(), + }; + Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) + }, _ => Err(format!("Unknown resource type: {resource_type}")), } }, @@ -151,6 +175,41 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res .map_err(|e| format!("Failed to parse input for Adapted/Three: {e}"))?; Ok(serde_json::to_string(&adapted_three).unwrap()) }, + "Adapted/SecurityContextElevated" => { + let adapted_security_context_elevated: AdaptedOne = serde_json::from_str(input) + .map_err(|e| format!("Failed to parse input for Adapted/SecurityContextElevated: {e}"))?; + Ok(serde_json::to_string(&adapted_security_context_elevated).unwrap()) + }, + "Adapted/SecurityContextRestricted" => { + let adapted_security_context_restricted: AdaptedOne = serde_json::from_str(input) + .map_err(|e| format!("Failed to parse input for Adapted/SecurityContextRestricted: {e}"))?; + Ok(serde_json::to_string(&adapted_security_context_restricted).unwrap()) + }, + "Adapted/SecurityContextCurrent" => { + let adapted_security_context_current: AdaptedOne = serde_json::from_str(input) + .map_err(|e| format!("Failed to parse input for Adapted/SecurityContextCurrent: {e}"))?; + Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) + }, + _ => Err(format!("Unknown resource type: {resource_type}")), + } + }, + AdapterOperation::Delete => { + match resource_type { + "Adapted/One" => { + if let Some(version) = resource_version && version != ADAPTED_ONE_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); + } + Ok(String::new()) + }, + "Adapted/Two" => { + if let Some(version) = resource_version && version != ADAPTED_TWO_VERSION { + return Err(format!("Unsupported version for {resource_type}: {version}")); + } + Ok(String::new()) + }, + "Adapted/SecurityContextElevated" | "Adapted/SecurityContextRestricted" | "Adapted/SecurityContextCurrent" => { + Ok(String::new()) + }, _ => Err(format!("Unknown resource type: {resource_type}")), } }, @@ -207,6 +266,30 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res println!("{}", serde_json::to_string(&adapted_three).unwrap()); std::process::exit(0); }, + "Adapted/SecurityContextElevated" => { + let adapted_security_context_elevated = AdaptedOne { + one: "elevated".to_string(), + name: None, + path: resource_path.clone(), + }; + Ok(serde_json::to_string(&adapted_security_context_elevated).unwrap()) + }, + "Adapted/SecurityContextRestricted" => { + let adapted_security_context_restricted = AdaptedOne { + one: "restricted".to_string(), + name: None, + path: resource_path.clone(), + }; + Ok(serde_json::to_string(&adapted_security_context_restricted).unwrap()) + }, + "Adapted/SecurityContextCurrent" => { + let adapted_security_context_current = AdaptedOne { + one: "current".to_string(), + name: None, + path: resource_path.clone(), + }; + Ok(serde_json::to_string(&adapted_security_context_current).unwrap()) + }, _ => Err(format!("Unknown resource type: {resource_type}")), } }, @@ -220,7 +303,7 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation, res let schema = schemars::schema_for!(AdaptedTwo); Ok(serde_json::to_string(&schema).unwrap()) }, - "Adapted/Three" => { + "Adapted/SecurityContextElevated" | "Adapted/SecurityContextRestricted" | "Adapted/SecurityContextCurrent" => { let schema = schemars::schema_for!(AdaptedOne); Ok(serde_json::to_string(&schema).unwrap()) }, diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index 135572bbe..93d36cd62 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -42,6 +42,7 @@ pub struct Args { pub enum AdapterOperation { Get, Set, + Delete, Test, List, Export, diff --git a/tools/test_group_resource/src/main.rs b/tools/test_group_resource/src/main.rs index 60176a3c9..a16791aa2 100644 --- a/tools/test_group_resource/src/main.rs +++ b/tools/test_group_resource/src/main.rs @@ -42,6 +42,7 @@ fn main() { }), ..Default::default() }), + adapted_manifest: None, }; let resource2 = DscResource { type_name: "Test/TestResource2".parse().unwrap(), @@ -71,6 +72,7 @@ fn main() { }), ..Default::default() }), + adapted_manifest: None, }; println!("{}", serde_json::to_string(&resource1).unwrap()); println!("{}", serde_json::to_string(&resource2).unwrap()); @@ -92,6 +94,7 @@ fn main() { adapted_content: None, target_resource: None, manifest: None, + adapted_manifest: None, schema: None, }; println!("{}", serde_json::to_string(&resource1).unwrap());