securitydept_basic_auth_context/config/
validator.rs1use securitydept_creds::BasicAuthCred;
2use securitydept_utils::redirect::RedirectTargetConfig;
3use serde::{Deserialize, Serialize};
4
5use super::BasicAuthContextConfigSource;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct BasicAuthContextConfigValidationError {
9 pub field_path: String,
10 pub code: String,
11 pub message: String,
12}
13
14impl BasicAuthContextConfigValidationError {
15 pub fn new(
16 field_path: impl Into<String>,
17 code: impl Into<String>,
18 message: impl Into<String>,
19 ) -> Self {
20 Self {
21 field_path: field_path.into(),
22 code: code.into(),
23 message: message.into(),
24 }
25 }
26}
27
28impl std::fmt::Display for BasicAuthContextConfigValidationError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(
31 f,
32 "basic_auth_context config validation failed for {} ({}): {}",
33 self.field_path, self.code, self.message
34 )
35 }
36}
37
38impl std::error::Error for BasicAuthContextConfigValidationError {}
39
40pub trait BasicAuthContextConfigValidator<Creds>
41where
42 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
43{
44 fn validate_basic_auth_context_config<S>(
45 &self,
46 config: &S,
47 ) -> Result<(), BasicAuthContextConfigValidationError>
48 where
49 S: BasicAuthContextConfigSource<Creds> + ?Sized;
50}
51
52#[derive(Debug, Clone, Copy, Default)]
53pub struct NoopBasicAuthContextConfigValidator;
54
55impl<Creds> BasicAuthContextConfigValidator<Creds> for NoopBasicAuthContextConfigValidator
56where
57 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
58{
59 fn validate_basic_auth_context_config<S>(
60 &self,
61 _config: &S,
62 ) -> Result<(), BasicAuthContextConfigValidationError>
63 where
64 S: BasicAuthContextConfigSource<Creds> + ?Sized,
65 {
66 Ok(())
67 }
68}
69
70impl<Creds, V> BasicAuthContextConfigValidator<Creds> for &V
71where
72 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
73 V: BasicAuthContextConfigValidator<Creds> + ?Sized,
74{
75 fn validate_basic_auth_context_config<S>(
76 &self,
77 config: &S,
78 ) -> Result<(), BasicAuthContextConfigValidationError>
79 where
80 S: BasicAuthContextConfigSource<Creds> + ?Sized,
81 {
82 (*self).validate_basic_auth_context_config(config)
83 }
84}
85
86impl<Creds, V> BasicAuthContextConfigValidator<Creds> for [V]
87where
88 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
89 V: BasicAuthContextConfigValidator<Creds>,
90{
91 fn validate_basic_auth_context_config<S>(
92 &self,
93 config: &S,
94 ) -> Result<(), BasicAuthContextConfigValidationError>
95 where
96 S: BasicAuthContextConfigSource<Creds> + ?Sized,
97 {
98 for validator in self {
99 validator.validate_basic_auth_context_config(config)?;
100 }
101
102 Ok(())
103 }
104}
105
106impl<Creds, V, const N: usize> BasicAuthContextConfigValidator<Creds> for [V; N]
107where
108 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
109 V: BasicAuthContextConfigValidator<Creds>,
110{
111 fn validate_basic_auth_context_config<S>(
112 &self,
113 config: &S,
114 ) -> Result<(), BasicAuthContextConfigValidationError>
115 where
116 S: BasicAuthContextConfigSource<Creds> + ?Sized,
117 {
118 self.as_slice().validate_basic_auth_context_config(config)
119 }
120}
121
122impl<Creds, V> BasicAuthContextConfigValidator<Creds> for Vec<V>
123where
124 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
125 V: BasicAuthContextConfigValidator<Creds>,
126{
127 fn validate_basic_auth_context_config<S>(
128 &self,
129 config: &S,
130 ) -> Result<(), BasicAuthContextConfigValidationError>
131 where
132 S: BasicAuthContextConfigSource<Creds> + ?Sized,
133 {
134 self.as_slice().validate_basic_auth_context_config(config)
135 }
136}
137
138impl<Creds, A, B> BasicAuthContextConfigValidator<Creds> for (A, B)
139where
140 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
141 A: BasicAuthContextConfigValidator<Creds>,
142 B: BasicAuthContextConfigValidator<Creds>,
143{
144 fn validate_basic_auth_context_config<S>(
145 &self,
146 config: &S,
147 ) -> Result<(), BasicAuthContextConfigValidationError>
148 where
149 S: BasicAuthContextConfigSource<Creds> + ?Sized,
150 {
151 self.0.validate_basic_auth_context_config(config)?;
152 self.1.validate_basic_auth_context_config(config)
153 }
154}
155
156impl<Creds, A, B, C> BasicAuthContextConfigValidator<Creds> for (A, B, C)
157where
158 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
159 A: BasicAuthContextConfigValidator<Creds>,
160 B: BasicAuthContextConfigValidator<Creds>,
161 C: BasicAuthContextConfigValidator<Creds>,
162{
163 fn validate_basic_auth_context_config<S>(
164 &self,
165 config: &S,
166 ) -> Result<(), BasicAuthContextConfigValidationError>
167 where
168 S: BasicAuthContextConfigSource<Creds> + ?Sized,
169 {
170 self.0.validate_basic_auth_context_config(config)?;
171 self.1.validate_basic_auth_context_config(config)?;
172 self.2.validate_basic_auth_context_config(config)
173 }
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct BasicAuthContextFixedSingleZonePathValidator {
178 zone_prefix: String,
179 login_subpath: String,
180 logout_subpath: String,
181}
182
183impl BasicAuthContextFixedSingleZonePathValidator {
184 pub fn new(
185 zone_prefix: impl Into<String>,
186 login_subpath: impl Into<String>,
187 logout_subpath: impl Into<String>,
188 ) -> Self {
189 Self {
190 zone_prefix: zone_prefix.into(),
191 login_subpath: login_subpath.into(),
192 logout_subpath: logout_subpath.into(),
193 }
194 }
195}
196
197impl<Creds> BasicAuthContextConfigValidator<Creds> for BasicAuthContextFixedSingleZonePathValidator
198where
199 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
200{
201 fn validate_basic_auth_context_config<S>(
202 &self,
203 config: &S,
204 ) -> Result<(), BasicAuthContextConfigValidationError>
205 where
206 S: BasicAuthContextConfigSource<Creds> + ?Sized,
207 {
208 if config.zones_config().len() != 1 {
209 return Err(BasicAuthContextConfigValidationError::new(
210 "zones",
211 "fixed_single_zone_required",
212 "expected exactly one basic-auth zone for the fixed single-zone host policy",
213 ));
214 }
215
216 let zone = &config.zones_config()[0];
217 if zone.zone_prefix != self.zone_prefix {
218 return Err(BasicAuthContextConfigValidationError::new(
219 "zones[0].zone_prefix",
220 "fixed_zone_path_conflict",
221 format!(
222 "basic-auth zone_prefix is fixed by the host to {}",
223 self.zone_prefix
224 ),
225 ));
226 }
227 if zone.login_subpath != self.login_subpath {
228 return Err(BasicAuthContextConfigValidationError::new(
229 "zones[0].login_subpath",
230 "fixed_zone_path_conflict",
231 format!(
232 "basic-auth login_subpath is fixed by the host to {}",
233 self.login_subpath
234 ),
235 ));
236 }
237 if zone.logout_subpath != self.logout_subpath {
238 return Err(BasicAuthContextConfigValidationError::new(
239 "zones[0].logout_subpath",
240 "fixed_zone_path_conflict",
241 format!(
242 "basic-auth logout_subpath is fixed by the host to {}",
243 self.logout_subpath
244 ),
245 ));
246 }
247
248 Ok(())
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub struct BasicAuthContextFixedPostAuthRedirectValidator {
254 post_auth_redirect: RedirectTargetConfig,
255}
256
257impl BasicAuthContextFixedPostAuthRedirectValidator {
258 pub fn new(post_auth_redirect: RedirectTargetConfig) -> Self {
259 Self { post_auth_redirect }
260 }
261}
262
263impl<Creds> BasicAuthContextConfigValidator<Creds>
264 for BasicAuthContextFixedPostAuthRedirectValidator
265where
266 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
267{
268 fn validate_basic_auth_context_config<S>(
269 &self,
270 config: &S,
271 ) -> Result<(), BasicAuthContextConfigValidationError>
272 where
273 S: BasicAuthContextConfigSource<Creds> + ?Sized,
274 {
275 if config.post_auth_redirect_config() != &self.post_auth_redirect {
276 return Err(BasicAuthContextConfigValidationError::new(
277 "post_auth_redirect",
278 "fixed_post_auth_redirect_conflict",
279 "basic-auth post_auth_redirect is fixed by the host and cannot be overridden",
280 ));
281 }
282
283 Ok(())
284 }
285}
286
287#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
288pub struct BasicAuthContextRejectZonePostAuthRedirectOverrideValidator;
289
290impl<Creds> BasicAuthContextConfigValidator<Creds>
291 for BasicAuthContextRejectZonePostAuthRedirectOverrideValidator
292where
293 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
294{
295 fn validate_basic_auth_context_config<S>(
296 &self,
297 config: &S,
298 ) -> Result<(), BasicAuthContextConfigValidationError>
299 where
300 S: BasicAuthContextConfigSource<Creds> + ?Sized,
301 {
302 if let Some((index, _)) = config
303 .zones_config()
304 .iter()
305 .enumerate()
306 .find(|(_, zone)| zone.post_auth_redirect.is_some())
307 {
308 return Err(BasicAuthContextConfigValidationError::new(
309 format!("zones[{index}].post_auth_redirect"),
310 "zone_post_auth_redirect_override_forbidden",
311 "zone-level basic-auth post_auth_redirect overrides are forbidden by the host",
312 ));
313 }
314
315 Ok(())
316 }
317}