Skip to main content

securitydept_token_set_context/access_token_substrate/
config.rs

1use securitydept_oauth_provider::OidcSharedConfig;
2use securitydept_oauth_resource_server::OAuthResourceServerConfig;
3use serde::Deserialize;
4
5use super::capabilities::TokenPropagation;
6use crate::orchestration::BackendConfigError;
7
8// ---------------------------------------------------------------------------
9// Config source trait
10// ---------------------------------------------------------------------------
11
12/// Trait for types that supply access-token substrate configuration components.
13///
14/// Mirrors [`BackendOidcModeConfigSource`] for the substrate layer.
15/// Implementors expose component-config accessors and gain default `resolve_*`
16/// helper methods that apply `[oidc]` shared defaults and validate each
17/// component.
18///
19/// [`BackendOidcModeConfigSource`]: crate::backend_oidc_mode::BackendOidcModeConfigSource
20pub trait AccessTokenSubstrateConfigSource {
21    // --- Component accessors ---
22
23    /// Access the raw `[oauth_resource_server]` config block.
24    fn resource_server_config(&self) -> &OAuthResourceServerConfig;
25
26    /// Access the token propagation capability axis.
27    fn token_propagation(&self) -> &TokenPropagation;
28
29    // --- Resolve helpers (default implementations) ---
30
31    /// Apply OIDC shared defaults to the resource-server config and validate.
32    ///
33    /// Delegates to [`OAuthResourceServerConfig::resolve_config`] which handles
34    /// `well_known_url`, `client_id`, `client_secret` inheritance from the
35    /// `[oidc]` shared block.
36    fn resolve_resource_server(
37        &self,
38        shared: &OidcSharedConfig,
39    ) -> Result<OAuthResourceServerConfig, BackendConfigError> {
40        let mut rs = self.resource_server_config().clone();
41        rs.resolve_config(shared)?;
42        Ok(rs)
43    }
44
45    /// **Recommended entry point.** Resolve all substrate sub-configs in one
46    /// step.
47    ///
48    /// When an `[oidc]` shared config is provided, resource-server config
49    /// inherits provider defaults. When `None`, resource-server config is
50    /// returned as-is (valid for deployments without OIDC discovery).
51    fn resolve_all(
52        &self,
53        shared: Option<&OidcSharedConfig>,
54    ) -> Result<ResolvedAccessTokenSubstrateConfig, BackendConfigError> {
55        let resource_server = if let Some(shared) = shared {
56            self.resolve_resource_server(shared)?
57        } else {
58            self.resource_server_config().clone()
59        };
60
61        Ok(ResolvedAccessTokenSubstrateConfig {
62            resource_server,
63            token_propagation: self.token_propagation().clone(),
64        })
65    }
66}
67
68// ---------------------------------------------------------------------------
69// Raw config (TOML / env deserialisable)
70// ---------------------------------------------------------------------------
71
72/// Unified configuration for the access-token substrate layer.
73///
74/// This struct owns the configuration for all cross-mode substrate concerns:
75/// resource-server verification and token propagation policy.
76///
77/// `resource_server` is optional at parse time — when absent it defaults to
78/// unconfigured. Call
79/// [`resolve_all`](AccessTokenSubstrateConfigSource::resolve_all) with the OIDC
80/// shared defaults to produce a [`ResolvedAccessTokenSubstrateConfig`].
81#[derive(Debug, Clone, Deserialize, Default)]
82pub struct AccessTokenSubstrateConfig {
83    /// OAuth resource-server verifier configuration.
84    #[serde(default, flatten)]
85    pub resource_server: OAuthResourceServerConfig,
86
87    /// Token propagation capability axis.
88    #[serde(default)]
89    pub token_propagation: TokenPropagation,
90}
91
92impl AccessTokenSubstrateConfigSource for AccessTokenSubstrateConfig {
93    fn resource_server_config(&self) -> &OAuthResourceServerConfig {
94        &self.resource_server
95    }
96
97    fn token_propagation(&self) -> &TokenPropagation {
98        &self.token_propagation
99    }
100}
101
102// ---------------------------------------------------------------------------
103// Resolved (validated) config
104// ---------------------------------------------------------------------------
105
106/// Validated configuration bundle for the access-token substrate.
107///
108/// Produced by [`AccessTokenSubstrateConfigSource::resolve_all`]. The
109/// `resource_server` field has had OIDC shared defaults applied and passed
110/// validation.
111#[derive(Debug, Clone)]
112pub struct ResolvedAccessTokenSubstrateConfig {
113    /// OAuth resource-server config with shared defaults applied.
114    pub resource_server: OAuthResourceServerConfig,
115    /// Token propagation capability axis (pass-through, no extra validation).
116    pub token_propagation: TokenPropagation,
117}
118
119#[cfg(test)]
120mod tests {
121    use securitydept_oauth_provider::{OAuthProviderRemoteConfig, OidcSharedConfig};
122    use securitydept_oauth_resource_server::OAuthResourceServerIntrospectionConfig;
123
124    use super::*;
125
126    #[test]
127    fn resolve_all_inherits_shared_defaults() {
128        let shared = OidcSharedConfig {
129            remote: OAuthProviderRemoteConfig {
130                well_known_url: Some(
131                    "https://auth.example.com/.well-known/openid-configuration".to_string(),
132                ),
133                ..Default::default()
134            },
135            client_id: Some("shared-app".to_string()),
136            client_secret: Some("shared-secret".to_string()),
137            ..Default::default()
138        };
139
140        let raw = AccessTokenSubstrateConfig {
141            resource_server: OAuthResourceServerConfig {
142                introspection: Some(OAuthResourceServerIntrospectionConfig::default()),
143                ..Default::default()
144            },
145            ..Default::default()
146        };
147
148        let resolved = raw.resolve_all(Some(&shared)).expect("should resolve");
149        assert_eq!(
150            resolved
151                .resource_server
152                .introspection
153                .as_ref()
154                .unwrap()
155                .client_id
156                .as_deref(),
157            Some("shared-app"),
158            "introspection.client_id should inherit from [oidc]"
159        );
160    }
161
162    #[test]
163    fn resolve_all_without_shared_returns_raw() {
164        let raw = AccessTokenSubstrateConfig {
165            resource_server: OAuthResourceServerConfig::default(),
166            token_propagation: TokenPropagation::Disabled,
167        };
168
169        let resolved = raw
170            .resolve_all(None)
171            .expect("should resolve without shared");
172        assert!(
173            resolved.resource_server.remote.well_known_url.is_none(),
174            "no shared defaults should be applied"
175        );
176        assert!(matches!(
177            resolved.token_propagation,
178            TokenPropagation::Disabled
179        ));
180    }
181
182    #[test]
183    fn resolve_all_propagation_axis_passes_through() {
184        use crate::access_token_substrate::propagation::{
185            PropagationDestinationPolicy, TokenPropagatorConfig,
186        };
187
188        let raw = AccessTokenSubstrateConfig {
189            resource_server: OAuthResourceServerConfig::default(),
190            token_propagation: TokenPropagation::Enabled {
191                config: TokenPropagatorConfig {
192                    destination_policy: PropagationDestinationPolicy {
193                        allowed_targets: vec![],
194                        ..Default::default()
195                    },
196                    ..Default::default()
197                },
198            },
199        };
200
201        let resolved = raw.resolve_all(None).expect("should resolve");
202        assert!(matches!(
203            resolved.token_propagation,
204            TokenPropagation::Enabled { .. }
205        ));
206    }
207}