1use async_trait::async_trait;
148use base64::Engine;
149use bytes::Bytes;
150use flate2::read::GzDecoder;
151use ipnet::Ipv4Net;
152use iprange::IpRange;
153use libunftp::auth::{AuthenticationError, Authenticator, DefaultUser};
154use ring::{
155 digest::SHA256_OUTPUT_LEN,
156 pbkdf2::{PBKDF2_HMAC_SHA256, verify},
157};
158use serde::Deserialize;
159use std::io::prelude::*;
160use std::{collections::HashMap, fs, num::NonZeroU32, path::Path, time::Duration};
161use tokio::time::sleep;
162use valid::{Validate, constraint::Length};
163
164#[derive(Deserialize, Clone, Debug)]
165struct ClientCertCredential {
166 allowed_cn: Option<String>,
167}
168
169#[derive(Deserialize, Clone, Debug)]
170#[serde(untagged)]
171enum Credentials {
172 Pbkdf2 {
173 username: String,
174 pbkdf2_salt: String,
175 pbkdf2_key: String,
176 pbkdf2_iter: NonZeroU32,
177 client_cert: Option<ClientCertCredential>,
178 allowed_ip_ranges: Option<Vec<String>>,
179 },
180 Plaintext {
181 username: String,
182 password: Option<String>,
183 client_cert: Option<ClientCertCredential>,
184 allowed_ip_ranges: Option<Vec<String>>,
185 },
186}
187
188#[derive(Clone, Debug)]
190pub struct JsonFileAuthenticator {
191 credentials_map: HashMap<String, UserCreds>,
192}
193
194#[derive(Clone, Debug)]
195enum Password {
196 PlainPassword {
197 password: Option<String>,
198 },
199 Pbkdf2Password {
200 pbkdf2_salt: Bytes,
201 pbkdf2_key: Bytes,
202 pbkdf2_iter: NonZeroU32,
203 },
204}
205
206#[derive(Clone, Debug)]
207struct UserCreds {
208 pub password: Password,
209 pub client_cert: Option<ClientCertCredential>,
210 pub allowed_ip_ranges: Option<IpRange<Ipv4Net>>,
211}
212
213impl JsonFileAuthenticator {
214 pub fn from_file<P: AsRef<Path>>(filename: P) -> Result<Self, Box<dyn std::error::Error>> {
216 let mut f = fs::File::open(&filename)?;
217
218 let mut magic: [u8; 4] = [0; 4];
221 let n = f.read(&mut magic[..])?;
222 let is_gz = n > 2 && magic[0] == 0x1F && magic[1] == 0x8B && magic[2] == 0x8;
223 let is_base64gz = n > 3 && magic[0] == b'H' && magic[1] == b'4' && magic[2] == b's' && magic[3] == b'I';
225
226 f.rewind()?;
227 let json: String = if is_gz | is_base64gz {
228 let mut gzdata: Vec<u8> = Vec::new();
229 if is_base64gz {
230 let mut b = Vec::new();
231 f.read_to_end(&mut b)?;
232 b.retain(|&x| x != b'\n' && x != b'\r');
233 gzdata = base64::engine::general_purpose::STANDARD.decode(b)?;
234 } else {
235 f.read_to_end(&mut gzdata)?;
236 }
237 let mut d = GzDecoder::new(&gzdata[..]);
238 let mut s = String::new();
239 d.read_to_string(&mut s)?;
240 s
241 } else {
242 let mut s = String::new();
243 f.read_to_string(&mut s)?;
244 s
245 };
246
247 Self::from_json(json)
248 }
249
250 pub fn from_json<T: Into<String>>(json: T) -> Result<Self, Box<dyn std::error::Error>> {
252 let credentials_list: Vec<Credentials> = serde_json::from_str::<Vec<Credentials>>(&json.into())?;
253 let map: Result<HashMap<String, UserCreds>, _> = credentials_list.into_iter().map(Self::list_entry_to_map_entry).collect();
254 Ok(JsonFileAuthenticator { credentials_map: map? })
255 }
256
257 fn list_entry_to_map_entry(user_info: Credentials) -> Result<(String, UserCreds), Box<dyn std::error::Error>> {
258 let map_entry = match user_info {
259 Credentials::Plaintext {
260 username,
261 password,
262 client_cert,
263 allowed_ip_ranges: ip_ranges,
264 } => (
265 username.clone(),
266 UserCreds {
267 password: Password::PlainPassword { password },
268 client_cert,
269 allowed_ip_ranges: Self::parse_ip_range(username, ip_ranges)?,
270 },
271 ),
272 Credentials::Pbkdf2 {
273 username,
274 pbkdf2_salt,
275 pbkdf2_key,
276 pbkdf2_iter,
277 client_cert,
278 allowed_ip_ranges: ip_ranges,
279 } => (
280 username.clone(),
281 UserCreds {
282 password: Password::Pbkdf2Password {
283 pbkdf2_salt: base64::engine::general_purpose::STANDARD
284 .decode(pbkdf2_salt)
285 .map_err(|_| "Could not base64 decode the salt")?
286 .into(),
287 pbkdf2_key: base64::engine::general_purpose::STANDARD
288 .decode(pbkdf2_key)
289 .map_err(|_| "Could not decode base64")?
290 .validate("pbkdf2_key", &Length::Max(SHA256_OUTPUT_LEN))
291 .result()
292 .map_err(|_| format!("Key of user \"{}\" is too long", username))?
293 .unwrap() .into(),
295 pbkdf2_iter,
296 },
297 client_cert,
298 allowed_ip_ranges: Self::parse_ip_range(username, ip_ranges)?,
299 },
300 ),
301 };
302 Ok(map_entry)
303 }
304
305 fn parse_ip_range(username: String, ip_ranges: Option<Vec<String>>) -> Result<Option<IpRange<Ipv4Net>>, String> {
306 ip_ranges
307 .map(|v| {
308 let range: Result<IpRange<Ipv4Net>, _> = v
309 .iter()
310 .map(|s| s.parse::<Ipv4Net>().map_err(|_| format!("could not parse IP ranges for user {}", username)))
311 .collect();
312 range
313 })
314 .transpose()
315 }
316
317 fn check_password(given_password: &str, actual_password: &Password) -> Result<(), ()> {
318 match actual_password {
319 Password::PlainPassword { password } => {
320 if let Some(pwd) = password {
321 if pwd == given_password { Ok(()) } else { Err(()) }
322 } else {
323 Err(())
324 }
325 }
326 Password::Pbkdf2Password {
327 pbkdf2_iter,
328 pbkdf2_salt,
329 pbkdf2_key,
330 } => verify(PBKDF2_HMAC_SHA256, *pbkdf2_iter, pbkdf2_salt, given_password.as_bytes(), pbkdf2_key).map_err(|_| ()),
331 }
332 }
333
334 fn ip_ok(creds: &libunftp::auth::Credentials, actual_creds: &UserCreds) -> bool {
335 match &actual_creds.allowed_ip_ranges {
336 Some(allowed) => match creds.source_ip {
337 std::net::IpAddr::V4(ref ip) => allowed.contains(ip),
338 _ => false,
339 },
340 None => true,
341 }
342 }
343}
344
345#[async_trait]
346impl Authenticator<DefaultUser> for JsonFileAuthenticator {
347 #[tracing_attributes::instrument]
348 async fn authenticate(&self, username: &str, creds: &libunftp::auth::Credentials) -> Result<DefaultUser, AuthenticationError> {
349 let res = if let Some(actual_creds) = self.credentials_map.get(username) {
350 let client_cert = &actual_creds.client_cert;
351 let certificate = &creds.certificate_chain.as_ref().and_then(|x| x.first());
352
353 let ip_check_result = if !Self::ip_ok(creds, actual_creds) {
354 Err(AuthenticationError::IpDisallowed)
355 } else {
356 Ok(DefaultUser {})
357 };
358
359 let cn_check_result = match (&client_cert, certificate) {
360 (Some(client_cert), Some(cert)) => match (&client_cert.allowed_cn, cert) {
364 (Some(cn), cert) => match cert.verify_cn(cn) {
365 Ok(is_authorized) => {
366 if is_authorized {
367 Some(Ok(DefaultUser {}))
368 } else {
369 Some(Err(AuthenticationError::CnDisallowed))
370 }
371 }
372 Err(e) => Some(Err(AuthenticationError::with_source("verify_cn", e))),
373 },
374 (None, _) => Some(Ok(DefaultUser {})),
375 },
376 (Some(_), None) => Some(Err(AuthenticationError::CnDisallowed)),
377 _ => None,
378 };
379
380 let pass_check_result = match &creds.password {
381 Some(given_password) => {
382 if Self::check_password(given_password, &actual_creds.password).is_ok() {
383 Some(Ok(DefaultUser {}))
384 } else {
385 Some(Err(AuthenticationError::BadPassword))
386 }
387 }
388 None => None,
389 };
390
391 match (pass_check_result, cn_check_result, ip_check_result) {
395 (None, None, _) => Err(AuthenticationError::BadPassword), (Some(pass_res), None, ip_res) => {
397 if pass_res.is_ok() {
398 ip_res
399 } else {
400 pass_res
401 }
402 }
403 (None, Some(cn_res), ip_res) => {
404 if cn_res.is_ok() {
405 ip_res
406 } else {
407 cn_res
408 }
409 }
410 (Some(pass_res), Some(cn_res), ip_res) => match (pass_res, cn_res) {
411 (Ok(_), Ok(_)) => ip_res,
412 (Ok(_), Err(e)) => Err(e),
413 (Err(e), Ok(_)) => Err(e),
414 (Err(e), Err(_)) => Err(e), },
416 }
417 } else {
418 Err(AuthenticationError::BadUser)
419 };
420
421 if res.is_err() {
422 sleep(Duration::from_millis(1500)).await;
423 }
424
425 res
426 }
427
428 async fn cert_auth_sufficient(&self, username: &str) -> bool {
438 if let Some(actual_creds) = self.credentials_map.get(username) {
439 if let Password::PlainPassword { password: None } = &actual_creds.password {
440 return actual_creds.client_cert.is_some();
441 }
442 }
443 false
444 }
445
446 fn name(&self) -> &str {
447 std::any::type_name::<Self>()
448 }
449}
450
451mod test {
452 #[allow(unused_imports)]
453 use libunftp::auth::ClientCert;
454
455 #[tokio::test]
456 async fn test_json_auth() {
457 use super::*;
458
459 let json: &str = r#"[
460 {
461 "username": "alice",
462 "pbkdf2_salt": "dGhpc2lzYWJhZHNhbHQ=",
463 "pbkdf2_key": "jZZ20ehafJPQPhUKsAAMjXS4wx9FSbzUgMn7HJqx4Hg=",
464 "pbkdf2_iter": 500000
465 },
466 {
467 "username": "bella",
468 "pbkdf2_salt": "dGhpc2lzYWJhZHNhbHR0b28=",
469 "pbkdf2_key": "C2kkRTybDzhkBGUkTn5Ys1LKPl8XINI46x74H4c9w8s=",
470 "pbkdf2_iter": 500000
471 },
472 {
473 "username": "carol",
474 "password": "not so secure"
475 },
476 {
477 "username": "dan",
478 "password": "",
479 "allowed_ip_ranges": ["127.0.0.1/8"]
480 }
481]"#;
482 let json_authenticator = JsonFileAuthenticator::from_json(json).unwrap();
483 assert_eq!(
484 json_authenticator
485 .authenticate("alice", &"this is the correct password for alice".into())
486 .await
487 .unwrap(),
488 DefaultUser
489 );
490 assert_eq!(
491 json_authenticator
492 .authenticate("bella", &"this is the correct password for bella".into())
493 .await
494 .unwrap(),
495 DefaultUser
496 );
497 assert_eq!(json_authenticator.authenticate("carol", &"not so secure".into()).await.unwrap(), DefaultUser);
498 assert_eq!(json_authenticator.authenticate("dan", &"".into()).await.unwrap(), DefaultUser);
499 assert!(matches!(
500 json_authenticator.authenticate("carol", &"this is the wrong password".into()).await,
501 Err(AuthenticationError::BadPassword)
502 ));
503 assert!(matches!(
504 json_authenticator.authenticate("bella", &"this is the wrong password".into()).await,
505 Err(AuthenticationError::BadPassword)
506 ));
507 assert!(matches!(
508 json_authenticator.authenticate("chuck", &"12345678".into()).await,
509 Err(AuthenticationError::BadUser)
510 ));
511
512 assert_eq!(
513 json_authenticator
514 .authenticate(
515 "dan",
516 &libunftp::auth::Credentials {
517 certificate_chain: None,
518 password: Some("".into()),
519 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
520 },
521 )
522 .await
523 .unwrap(),
524 DefaultUser
525 );
526
527 match json_authenticator
528 .authenticate(
529 "dan",
530 &libunftp::auth::Credentials {
531 certificate_chain: None,
532 password: Some("".into()),
533 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(128, 0, 0, 1)),
534 },
535 )
536 .await
537 {
538 Err(AuthenticationError::IpDisallowed) => (),
539 _ => panic!(),
540 }
541 }
542
543 #[tokio::test]
544 async fn test_json_cert_sufficient() {
545 use super::*;
546
547 let json: &str = r#"[
548 {
549 "username": "alice",
550 "password": "has a password"
551 },
552 {
553 "username": "bob",
554 "client_cert": {
555 "allowed_cn": "my.cert.is.everything"
556 }
557 },
558 {
559 "username": "carol",
560 "password": "This is ultimate security.",
561 "client_cert": {
562 "allowed_cn": "i.am.trusted"
563 }
564 },
565 {
566 "username": "dan",
567 "pbkdf2_salt": "dGhpc2lzYWJhZHNhbHQ=",
568 "pbkdf2_key": "jZZ20ehafJPQPhUKsAAMjXS4wx9FSbzUgMn7HJqx4Hg=",
569 "pbkdf2_iter": 500000
570 },
571 {
572 "username": "eve",
573 "pbkdf2_salt": "dGhpc2lzYWJhZHNhbHR0b28=",
574 "pbkdf2_key": "C2kkRTybDzhkBGUkTn5Ys1LKPl8XINI46x74H4c9w8s=",
575 "pbkdf2_iter": 500000,
576 "client_cert": {
577 "allowed_cn": "i.am.trusted"
578 }
579 },
580 {
581 "username": "freddie",
582 "client_cert": {}
583 },
584 {
585 "username": "santa",
586 "password": "clara",
587 "client_cert": {}
588 }
589]"#;
590 let json_authenticator = JsonFileAuthenticator::from_json(json).unwrap();
591 assert!(!json_authenticator.cert_auth_sufficient("alice").await);
592 assert!(json_authenticator.cert_auth_sufficient("bob").await);
593 assert!(!json_authenticator.cert_auth_sufficient("carol").await);
594 assert!(!json_authenticator.cert_auth_sufficient("dan").await);
595 assert!(!json_authenticator.cert_auth_sufficient("eve").await);
596 assert!(json_authenticator.cert_auth_sufficient("freddie").await);
597 assert!(!json_authenticator.cert_auth_sufficient("santa").await);
598 }
599
600 #[tokio::test]
601 async fn test_json_cert_authenticate() {
602 use super::*;
603
604 let cert: &[u8] = &[
606 0x30, 0x82, 0x03, 0x1f, 0x30, 0x82, 0x02, 0x07, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0xc3, 0x3d, 0x48, 0x52, 0x68, 0x7e, 0x06, 0x83,
607 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x40, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55,
608 0x04, 0x03, 0x0c, 0x13, 0x75, 0x6e, 0x66, 0x74, 0x70, 0x2d, 0x63, 0x61, 0x2e, 0x6d, 0x79, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
609 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0a, 0x6d, 0x79, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x0b, 0x30, 0x09,
610 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4e, 0x4c, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x30, 0x36, 0x32, 0x35, 0x31, 0x32, 0x30, 0x38, 0x30,
611 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x34, 0x31, 0x34, 0x31, 0x32, 0x30, 0x38, 0x30, 0x38, 0x5a, 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x06,
612 0x03, 0x55, 0x04, 0x03, 0x0c, 0x17, 0x75, 0x6e, 0x66, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x79, 0x73, 0x69, 0x74,
613 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0a, 0x6d, 0x79, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x63,
614 0x6f, 0x6d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4e, 0x4c, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
615 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
616 0xec, 0xdf, 0x85, 0x48, 0xf4, 0x20, 0xdd, 0x52, 0x0b, 0x9c, 0x08, 0x6a, 0x78, 0x0f, 0x16, 0x16, 0x8b, 0x11, 0x79, 0xef, 0x32, 0xb6, 0x55, 0x90,
617 0x50, 0x31, 0x09, 0xf6, 0x1a, 0x99, 0xff, 0xa2, 0x51, 0x0f, 0x74, 0x2b, 0x80, 0xeb, 0x69, 0x8e, 0x42, 0x53, 0x54, 0x7d, 0xf0, 0x13, 0x92, 0x2d,
618 0x86, 0xda, 0x3b, 0x7d, 0x2b, 0x19, 0x15, 0x3a, 0xeb, 0xb0, 0xd8, 0x33, 0xb4, 0x4c, 0xb0, 0x4e, 0x63, 0x32, 0x35, 0x8e, 0x30, 0xc9, 0xfe, 0xaf,
619 0xcc, 0xc7, 0xa6, 0xdc, 0xbf, 0x83, 0x16, 0x6f, 0xdc, 0xc5, 0xdf, 0x10, 0x24, 0x45, 0xb0, 0x7c, 0x5b, 0x36, 0xc7, 0xcd, 0xf7, 0x5b, 0x1e, 0x9f,
620 0xae, 0x80, 0xd8, 0x0e, 0x27, 0x0f, 0xb6, 0x04, 0x16, 0xa5, 0x4b, 0x58, 0x4c, 0xd5, 0x25, 0x1b, 0x99, 0x48, 0xd4, 0x02, 0x85, 0x25, 0x54, 0x31,
621 0x2b, 0x77, 0x4d, 0xe9, 0x81, 0xbe, 0x81, 0x32, 0xee, 0x16, 0x59, 0x21, 0x82, 0x8c, 0x7d, 0x9f, 0xca, 0x93, 0xe4, 0x93, 0xb8, 0x2f, 0x0f, 0x16,
622 0xa6, 0x43, 0x3e, 0xa6, 0x4f, 0xe0, 0xbd, 0xd5, 0x30, 0x05, 0x8e, 0xe1, 0x85, 0x12, 0xee, 0xbe, 0xa0, 0x1a, 0xa0, 0x63, 0x16, 0x3c, 0xf7, 0x73,
623 0xe1, 0xe6, 0x76, 0xe5, 0x98, 0x82, 0x59, 0x88, 0xe4, 0xa4, 0xe2, 0xf9, 0xc7, 0xb8, 0x21, 0x4c, 0x3f, 0x9f, 0xeb, 0x06, 0x13, 0xf8, 0x67, 0x45,
624 0x4e, 0xf0, 0xf8, 0x07, 0x59, 0x1f, 0x9d, 0x52, 0xb9, 0x19, 0xdb, 0x0e, 0x36, 0x92, 0x39, 0x85, 0xa5, 0x18, 0x30, 0x9f, 0x6b, 0x39, 0x9c, 0xba,
625 0x09, 0xf0, 0xc5, 0xfc, 0x21, 0xf0, 0x27, 0xf9, 0x97, 0x45, 0x96, 0x38, 0x25, 0x56, 0x59, 0x18, 0x9c, 0x99, 0x75, 0x0a, 0x86, 0xb8, 0xc1, 0xb6,
626 0x2c, 0xbe, 0x53, 0x4a, 0xe8, 0xd2, 0x8a, 0xf8, 0x47, 0xc3, 0x71, 0x60, 0x28, 0x88, 0xe1, 0x13, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x18, 0x30,
627 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x0d, 0x30, 0x0b, 0x82, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x30,
628 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xb4, 0x57, 0x68, 0x3e, 0x4b,
629 0x9b, 0x89, 0xbd, 0x60, 0x2b, 0xa7, 0x02, 0x04, 0xbf, 0xff, 0x56, 0xd5, 0xce, 0x1c, 0x20, 0x7f, 0x92, 0xa9, 0xd3, 0xf3, 0x8b, 0xfd, 0x8c, 0x41,
630 0x19, 0xb5, 0xe5, 0x01, 0x0a, 0x5f, 0x2f, 0x86, 0x0b, 0x26, 0x71, 0x89, 0x7b, 0x0f, 0x2c, 0x1b, 0x54, 0xc9, 0x3a, 0xf4, 0x37, 0xdf, 0x52, 0x7d,
631 0x87, 0x30, 0x49, 0xbf, 0x7c, 0x84, 0x46, 0x3c, 0x21, 0xbe, 0x99, 0x8f, 0x69, 0x56, 0x8c, 0x5f, 0x7c, 0xb0, 0xe9, 0xdc, 0xbd, 0xfa, 0xbe, 0x26,
632 0xb6, 0xfa, 0xa5, 0xdd, 0x9b, 0x41, 0xe9, 0x2c, 0xd2, 0x21, 0x42, 0xe7, 0x67, 0xcc, 0x01, 0xda, 0x7a, 0xb7, 0x84, 0xa7, 0x83, 0x91, 0x37, 0x43,
633 0x04, 0x3e, 0xde, 0x41, 0xba, 0x7d, 0xa3, 0x5c, 0xc0, 0x6f, 0x8c, 0x2c, 0x1c, 0xa8, 0x86, 0xa7, 0x38, 0xa4, 0x1f, 0x58, 0x7d, 0xb2, 0xf7, 0xc8,
634 0xe2, 0x3c, 0x10, 0xd9, 0x69, 0x4b, 0xef, 0x3d, 0x47, 0x39, 0xf8, 0x3e, 0x87, 0x67, 0x7e, 0xfc, 0x43, 0xbb, 0x01, 0x7c, 0xa2, 0x26, 0xb9, 0xb1,
635 0x3c, 0x1d, 0xd4, 0xbe, 0xa0, 0x02, 0x0d, 0x10, 0x62, 0xd9, 0xe3, 0x7f, 0x90, 0x30, 0x89, 0x64, 0x37, 0x90, 0xcd, 0x34, 0xd4, 0x03, 0x9f, 0x96,
636 0x80, 0xb1, 0xaa, 0x93, 0x59, 0x23, 0xd7, 0xad, 0x3e, 0x13, 0x76, 0x02, 0x1f, 0xd2, 0xa6, 0x8b, 0x44, 0x26, 0x8f, 0x1d, 0xf8, 0x60, 0xba, 0xc5,
637 0x52, 0x31, 0x26, 0x64, 0xca, 0x7e, 0x3f, 0xe9, 0xba, 0x72, 0xdc, 0x80, 0xfd, 0x4b, 0x10, 0x66, 0x5d, 0x85, 0xd3, 0xa3, 0x2b, 0xe6, 0x73, 0x4a,
638 0xcf, 0xba, 0xe0, 0x48, 0x4f, 0x00, 0xed, 0xaa, 0xb3, 0x75, 0xe8, 0xbc, 0xf3, 0xba, 0xb7, 0x4d, 0x59, 0x17, 0xde, 0xb5, 0x2c, 0x8d, 0x9a, 0x88,
639 0x34, 0x02, 0x19, 0x9c, 0x22, 0x56, 0x26, 0x3f, 0x3a, 0x6f, 0x0f,
640 ];
641
642 let json: &str = r#"[
643 {
644 "username": "alice",
645 "password": "has a password",
646 "client_cert": {
647 "allowed_cn": "unftp-client.mysite.com"
648 }
649 },
650 {
651 "username": "bob",
652 "client_cert": {
653 "allowed_cn": "unftp-client.mysite.com"
654 }
655 },
656 {
657 "username": "carol",
658 "client_cert": {
659 "allowed_cn": "unftp-other-client.mysite.com"
660 }
661 },
662 {
663 "username": "dean",
664 "client_cert": {}
665 }
666]"#;
667 let json_authenticator = JsonFileAuthenticator::from_json(json).unwrap();
668 let client_cert: Vec<u8> = cert.to_vec();
669
670 assert_eq!(
672 json_authenticator
673 .authenticate(
674 "alice",
675 &libunftp::auth::Credentials {
676 certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
677 password: Some("has a password".into()),
678 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
679 },
680 )
681 .await
682 .unwrap(),
683 DefaultUser
684 );
685
686 match json_authenticator
688 .authenticate(
689 "alice",
690 &libunftp::auth::Credentials {
691 certificate_chain: None,
692 password: Some("has a password".into()),
693 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
694 },
695 )
696 .await
697 {
698 Err(AuthenticationError::CnDisallowed) => (),
699 _ => panic!(),
700 }
701
702 assert_eq!(
704 json_authenticator
705 .authenticate(
706 "bob",
707 &libunftp::auth::Credentials {
708 certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
709 password: None,
710 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
711 },
712 )
713 .await
714 .unwrap(),
715 DefaultUser
716 );
717
718 match json_authenticator
720 .authenticate(
721 "carol",
722 &libunftp::auth::Credentials {
723 certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
724 password: None,
725 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
726 },
727 )
728 .await
729 {
730 Err(AuthenticationError::CnDisallowed) => (),
731 _ => panic!(),
732 }
733
734 assert_eq!(
736 json_authenticator
737 .authenticate(
738 "dean",
739 &libunftp::auth::Credentials {
740 certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
741 password: None,
742 source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
743 },
744 )
745 .await
746 .unwrap(),
747 DefaultUser
748 );
749 }
750}