Files
Stalwart/tests/src/jmap/auth_limits.rs

258 lines
7.3 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{
net::{IpAddr, Ipv4Addr},
sync::Arc,
time::Duration,
};
use common::listener::blocked::BLOCKED_IP_KEY;
use imap_proto::ResponseType;
use jmap_client::{
client::{Client, Credentials},
core::set::{SetError, SetErrorType},
mailbox::{self},
};
use jmap_proto::types::id::Id;
use store::write::now;
use crate::{
directory::internal::TestInternalDirectory,
imap::{ImapConnection, Type},
jmap::{assert_is_empty, mailbox::destroy_all_mailboxes},
};
use super::JMAPTest;
pub async fn test(params: &mut JMAPTest) {
println!("Running Authorization tests...");
// Create test account
let server = params.server.clone();
let account_id = Id::from(
server
.core
.storage
.data
.create_test_user(
"jdoe@example.com",
"12345",
"John Doe",
&["jdoe@example.com", "john.doe@example.com"],
)
.await,
)
.to_string();
// Reset rate limiters
params.webhook.clear();
// Incorrect passwords should be rejected with a 401 error
assert!(matches!(
Client::new()
.credentials(Credentials::basic("jdoe@example.com", "abcde"))
.accept_invalid_certs(true) .follow_redirects(["127.0.0.1"])
.connect("https://127.0.0.1:8899")
.await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(401)));
// Wait until the beginning of the 5 seconds bucket
const LIMIT: u64 = 5;
let now = now();
let range_start = now / LIMIT;
let range_end = (range_start * LIMIT) + LIMIT;
tokio::time::sleep(Duration::from_secs(range_end - now)).await;
// Test fail2ban
assert_eq!(
server
.core
.storage
.config
.get(format!("{BLOCKED_IP_KEY}.127.0.0.1"))
.await
.unwrap(),
None
);
for n in 0..98 {
match Client::new()
.credentials(Credentials::basic(
"not_an_account@example.com",
&format!("brute_force{}", n),
))
.accept_invalid_certs(true)
.follow_redirects(["127.0.0.1"])
.connect("https://127.0.0.1:8899")
.await
{
Err(jmap_client::Error::Problem(_)) => {}
Err(err) => {
panic!("Unexpected response: {:?}", err);
}
Ok(_) => {
panic!("Unexpected success");
}
}
}
let mut imap = ImapConnection::connect(b"_x ").await;
imap.send("AUTHENTICATE PLAIN AGpvaG4AY2hpbWljaGFuZ2Fz")
.await;
imap.assert_read(Type::Tagged, ResponseType::No).await;
// There are already 100 failed login attempts for this IP address
// so the next one should be rejected, even if done over IMAP
imap.send("AUTHENTICATE PLAIN AGpvaG4AY2hpbWljaGFuZ2Fz")
.await;
imap.assert_disconnect().await;
// Make sure the IP address is blocked
assert_eq!(
server
.core
.storage
.config
.get(format!("{BLOCKED_IP_KEY}.127.0.0.1"))
.await
.unwrap(),
Some(String::new())
);
ImapConnection::connect(b"_y ")
.await
.assert_disconnect()
.await;
// Lift ban
server
.core
.storage
.config
.clear(format!("{BLOCKED_IP_KEY}.127.0.0.1"))
.await
.unwrap();
server
.inner
.data
.blocked_ips
.write()
.remove(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
// Valid authentication requests should not be rate limited
for _ in 0..110 {
Client::new()
.credentials(Credentials::basic("jdoe@example.com", "12345"))
.accept_invalid_certs(true)
.follow_redirects(["127.0.0.1"])
.connect("https://127.0.0.1:8899")
.await
.unwrap();
}
// Login with the correct credentials
let client = Client::new()
.credentials(Credentials::basic("jdoe@example.com", "12345"))
.accept_invalid_certs(true)
.follow_redirects(["127.0.0.1"])
.connect("https://127.0.0.1:8899")
.await
.unwrap();
assert_eq!(client.session().username(), "jdoe@example.com");
assert_eq!(
client.session().account(&account_id).unwrap().name(),
"John Doe"
);
assert!(client.session().account(&account_id).unwrap().is_personal());
// Uploads up to 5000000 bytes should be allowed
assert_eq!(
client
.upload(None, vec![b'A'; 5000000], None)
.await
.unwrap()
.size(),
5000000
);
assert!(
client
.upload(None, vec![b'A'; 5000001], None)
.await
.is_err()
);
// Users should be allowed to create identities only
// using email addresses associated to their principal
let iid1 = client
.identity_create("John Doe", "jdoe@example.com")
.await
.unwrap()
.take_id();
let iid2 = client
.identity_create("John Doe (secondary)", "john.doe@example.com")
.await
.unwrap()
.take_id();
assert!(matches!(
client
.identity_create("John the Spammer", "spammy@mcspamface.com")
.await,
Err(jmap_client::Error::Set(SetError {
type_: SetErrorType::InvalidProperties,
..
}))
));
client.identity_destroy(&iid1).await.unwrap();
client.identity_destroy(&iid2).await.unwrap();
// Concurrent requests check
let client = Arc::new(client);
for _ in 0..8 {
let client_ = client.clone();
tokio::spawn(async move {
let _ = client_
.mailbox_query(
mailbox::query::Filter::name("__sleep").into(),
[mailbox::query::Comparator::name()].into(),
)
.await;
});
}
tokio::time::sleep(Duration::from_millis(500)).await;
assert!(matches!(
client
.mailbox_query(
mailbox::query::Filter::name("__sleep").into(),
[mailbox::query::Comparator::name()].into(),
)
.await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));
// Wait for sleep to be done
tokio::time::sleep(Duration::from_millis(1000)).await;
// Concurrent upload test
for _ in 0..4 {
let client_ = client.clone();
tokio::spawn(async move {
client_.upload(None, b"sleep".to_vec(), None).await.unwrap();
});
}
tokio::time::sleep(Duration::from_millis(500)).await;
assert!(matches!(
client.upload(None, b"sleep".to_vec(), None).await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));
// Destroy test accounts
params.client.set_default_account_id(&account_id);
destroy_all_mailboxes(params).await;
assert_is_empty(server).await;
// Check webhook events
params
.webhook
.assert_contains(&["auth.failed", "auth.success", "security.authentication-ban"]);
}