securitydept_token_set_context/access_token_substrate/config/
mod.rs1use securitydept_oauth_provider::OidcSharedConfig;
2use securitydept_oauth_resource_server::OAuthResourceServerConfig;
3use serde::Deserialize;
4
5use super::capabilities::TokenPropagation;
6use crate::orchestration::BackendConfigError;
7
8pub mod validator;
9
10pub use validator::{
11 AccessTokenSubstrateConfigValidationError, AccessTokenSubstrateConfigValidator,
12 NoopAccessTokenSubstrateConfigValidator,
13};
14
15pub trait AccessTokenSubstrateConfigSource {
28 fn resource_server_config(&self) -> &OAuthResourceServerConfig;
32
33 fn token_propagation(&self) -> &TokenPropagation;
35
36 fn resolve_resource_server(
44 &self,
45 shared: &OidcSharedConfig,
46 ) -> Result<OAuthResourceServerConfig, BackendConfigError> {
47 let mut rs = self.resource_server_config().clone();
48 rs.resolve_config(shared)?;
49 Ok(rs)
50 }
51
52 fn resolve_all(
59 &self,
60 shared: Option<&OidcSharedConfig>,
61 ) -> Result<ResolvedAccessTokenSubstrateConfig, BackendConfigError> {
62 let validator = NoopAccessTokenSubstrateConfigValidator;
63 self.resolve_all_with_validator(shared, &validator)
64 }
65
66 fn resolve_all_with_validator<V>(
67 &self,
68 shared: Option<&OidcSharedConfig>,
69 validator: &V,
70 ) -> Result<ResolvedAccessTokenSubstrateConfig, BackendConfigError>
71 where
72 V: AccessTokenSubstrateConfigValidator,
73 {
74 let raw_config = AccessTokenSubstrateConfig {
75 resource_server: self.resource_server_config().clone(),
76 token_propagation: self.token_propagation().clone(),
77 };
78 validator
79 .validate_raw_access_token_substrate_config(&raw_config)
80 .map_err(BackendConfigError::AccessTokenSubstrateValidation)?;
81
82 let resource_server = if let Some(shared) = shared {
83 self.resolve_resource_server(shared)?
84 } else {
85 self.resource_server_config().clone()
86 };
87
88 Ok(ResolvedAccessTokenSubstrateConfig {
89 resource_server,
90 token_propagation: self.token_propagation().clone(),
91 })
92 }
93}
94
95#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
109#[derive(Debug, Clone, Deserialize, Default)]
110pub struct AccessTokenSubstrateConfig {
111 #[serde(default, flatten)]
113 pub resource_server: OAuthResourceServerConfig,
114
115 #[serde(default)]
117 pub token_propagation: TokenPropagation,
118}
119
120impl AccessTokenSubstrateConfigSource for AccessTokenSubstrateConfig {
121 fn resource_server_config(&self) -> &OAuthResourceServerConfig {
122 &self.resource_server
123 }
124
125 fn token_propagation(&self) -> &TokenPropagation {
126 &self.token_propagation
127 }
128}
129
130#[derive(Debug, Clone)]
140pub struct ResolvedAccessTokenSubstrateConfig {
141 pub resource_server: OAuthResourceServerConfig,
143 pub token_propagation: TokenPropagation,
145}
146
147#[cfg(test)]
148mod tests {
149 use std::sync::{
150 Arc,
151 atomic::{AtomicUsize, Ordering},
152 };
153
154 use securitydept_oauth_provider::{OAuthProviderRemoteConfig, OidcSharedConfig};
155 use securitydept_oauth_resource_server::OAuthResourceServerIntrospectionConfig;
156 use securitydept_utils::secret::SecretString;
157
158 use super::*;
159
160 #[test]
161 fn resolve_all_inherits_shared_defaults() {
162 let shared = OidcSharedConfig {
163 remote: OAuthProviderRemoteConfig {
164 well_known_url: Some(
165 "https://auth.example.com/.well-known/openid-configuration".to_string(),
166 ),
167 ..Default::default()
168 },
169 client_id: Some("shared-app".to_string()),
170 client_secret: Some(SecretString::from("shared-secret")),
171 ..Default::default()
172 };
173
174 let raw = AccessTokenSubstrateConfig {
175 resource_server: OAuthResourceServerConfig {
176 introspection: Some(OAuthResourceServerIntrospectionConfig::default()),
177 ..Default::default()
178 },
179 ..Default::default()
180 };
181
182 let resolved = raw.resolve_all(Some(&shared)).expect("should resolve");
183 assert_eq!(
184 resolved
185 .resource_server
186 .introspection
187 .as_ref()
188 .unwrap()
189 .client_id
190 .as_deref(),
191 Some("shared-app"),
192 "introspection.client_id should inherit from [oidc]"
193 );
194 }
195
196 #[test]
197 fn resolve_all_without_shared_returns_raw() {
198 let raw = AccessTokenSubstrateConfig {
199 resource_server: OAuthResourceServerConfig::default(),
200 token_propagation: TokenPropagation::Disabled,
201 };
202
203 let resolved = raw
204 .resolve_all(None)
205 .expect("should resolve without shared");
206 assert!(
207 resolved.resource_server.remote.well_known_url.is_none(),
208 "no shared defaults should be applied"
209 );
210 assert!(matches!(
211 resolved.token_propagation,
212 TokenPropagation::Disabled
213 ));
214 }
215
216 #[test]
217 fn resolve_all_propagation_axis_passes_through() {
218 use crate::access_token_substrate::propagation::{
219 PropagationDestinationPolicy, TokenPropagatorConfig,
220 };
221
222 let raw = AccessTokenSubstrateConfig {
223 resource_server: OAuthResourceServerConfig::default(),
224 token_propagation: TokenPropagation::Enabled {
225 config: TokenPropagatorConfig {
226 destination_policy: PropagationDestinationPolicy {
227 allowed_targets: vec![],
228 ..Default::default()
229 },
230 ..Default::default()
231 },
232 },
233 };
234
235 let resolved = raw.resolve_all(None).expect("should resolve");
236 assert!(matches!(
237 resolved.token_propagation,
238 TokenPropagation::Enabled { .. }
239 ));
240 }
241
242 #[test]
243 fn resolve_all_with_validator_rejects_raw_config() {
244 struct RejectEnabledPropagation;
245
246 impl AccessTokenSubstrateConfigValidator for RejectEnabledPropagation {
247 fn validate_raw_access_token_substrate_config(
248 &self,
249 config: &AccessTokenSubstrateConfig,
250 ) -> Result<(), AccessTokenSubstrateConfigValidationError> {
251 if matches!(config.token_propagation, TokenPropagation::Enabled { .. }) {
252 return Err(AccessTokenSubstrateConfigValidationError::new(
253 "token_propagation",
254 "disabled_by_host",
255 "token propagation is disabled by the host",
256 ));
257 }
258
259 Ok(())
260 }
261 }
262
263 use crate::access_token_substrate::propagation::TokenPropagatorConfig;
264
265 let raw = AccessTokenSubstrateConfig {
266 token_propagation: TokenPropagation::Enabled {
267 config: TokenPropagatorConfig::default(),
268 },
269 ..Default::default()
270 };
271
272 let error = raw
273 .resolve_all_with_validator(None, &RejectEnabledPropagation)
274 .expect_err("validator should reject enabled propagation");
275
276 assert!(matches!(
277 error,
278 BackendConfigError::AccessTokenSubstrateValidation(ref validation)
279 if validation.field_path == "token_propagation"
280 && validation.code == "disabled_by_host"
281 ));
282 }
283
284 #[test]
285 fn resolve_all_with_validator_accepts_validator_composition() {
286 #[derive(Clone)]
287 struct CountValidator(Arc<AtomicUsize>);
288
289 impl AccessTokenSubstrateConfigValidator for CountValidator {
290 fn validate_raw_access_token_substrate_config(
291 &self,
292 _config: &AccessTokenSubstrateConfig,
293 ) -> Result<(), AccessTokenSubstrateConfigValidationError> {
294 self.0.fetch_add(1, Ordering::SeqCst);
295 Ok(())
296 }
297 }
298
299 let calls = Arc::new(AtomicUsize::new(0));
300 let validators = [
301 CountValidator(Arc::clone(&calls)),
302 CountValidator(Arc::clone(&calls)),
303 ];
304 let raw = AccessTokenSubstrateConfig::default();
305
306 raw.resolve_all_with_validator(None, &validators)
307 .expect("composed validators should pass");
308
309 assert_eq!(calls.load(Ordering::SeqCst), 2);
310 }
311}