Skip to content

Commit

Permalink
OIDC diretory
Browse files Browse the repository at this point in the history
  • Loading branch information
mdecimus committed Oct 2, 2024
1 parent 6e2cd78 commit 1d9ac04
Show file tree
Hide file tree
Showing 40 changed files with 915 additions and 109 deletions.
30 changes: 17 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <[email protected]>"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
repository = "https://github.com/stalwartlabs/cli"
homepage = "https://github.com/stalwartlabs/cli"
version = "0.10.1"
version = "0.10.2"
edition = "2021"
readme = "README.md"
resolver = "2"
Expand Down
2 changes: 1 addition & 1 deletion crates/common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "common"
version = "0.10.1"
version = "0.10.2"
edition = "2021"
resolver = "2"

Expand Down
38 changes: 23 additions & 15 deletions crates/common/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ pub struct AuthRequest<'x> {

impl Server {
pub async fn authenticate(&self, req: &AuthRequest<'_>) -> trc::Result<Arc<AccessToken>> {
// Resolve directory
let directory = req.directory.unwrap_or(&self.core.storage.directory);

// Validate credentials
match &req.credentials {
Credentials::OAuthBearer { token } => {
Credentials::OAuthBearer { token } if !directory.has_bearer_token_support() => {
match self
.validate_access_token(GrantType::AccessToken.into(), token)
.await
Expand All @@ -68,7 +71,7 @@ impl Server {
Err(err) => Err(err),
}
}
_ => match self.authenticate_plain(req).await {
_ => match self.authenticate_credentials(req, directory).await {
Ok(principal) => {
if let Some(access_token) =
self.inner.data.access_tokens.get_with_ttl(&principal.id())
Expand All @@ -94,9 +97,11 @@ impl Server {
})
}

async fn authenticate_plain(&self, req: &AuthRequest<'_>) -> trc::Result<Principal> {
let directory = req.directory.unwrap_or(&self.core.storage.directory);

async fn authenticate_credentials(
&self,
req: &AuthRequest<'_>,
directory: &Directory,
) -> trc::Result<Principal> {
// First try to authenticate the user against the default directory
let result = match directory
.query(QueryBy::Credentials(&req.credentials), req.return_member_of)
Expand All @@ -105,10 +110,9 @@ impl Server {
Ok(Some(principal)) => {
trc::event!(
Auth(trc::AuthEvent::Success),
AccountName = req.credentials.login().to_string(),
AccountName = principal.name().to_string(),
AccountId = principal.id(),
SpanId = req.session_id,
Type = principal.typ().as_str(),
);

return Ok(principal);
Expand Down Expand Up @@ -176,16 +180,19 @@ impl Server {
Err(trc::SecurityEvent::AuthenticationBan
.into_err()
.ctx(trc::Key::RemoteIp, req.remote_ip)
.ctx(trc::Key::AccountName, login.to_string()))
.ctx_opt(trc::Key::AccountName, login.map(|s| s.to_string())))
} else {
Err(trc::AuthEvent::Failed
.ctx(trc::Key::RemoteIp, req.remote_ip)
.ctx(trc::Key::AccountName, login.to_string()))
.ctx_opt(trc::Key::AccountName, login.map(|s| s.to_string())))
}
} else {
Err(trc::AuthEvent::Failed
.ctx(trc::Key::RemoteIp, req.remote_ip)
.ctx(trc::Key::AccountName, req.credentials.login().to_string()))
.ctx_opt(
trc::Key::AccountName,
req.credentials.login().map(|s| s.to_string()),
))
}
}

Expand Down Expand Up @@ -241,15 +248,16 @@ impl<'x> AuthRequest<'x> {
}

pub(crate) trait CredentialsUsername {
fn login(&self) -> &str;
fn login(&self) -> Option<&str>;
}

impl CredentialsUsername for Credentials<String> {
fn login(&self) -> &str {
fn login(&self) -> Option<&str> {
match self {
Credentials::Plain { username, .. }
| Credentials::XOauth2 { username, .. }
| Credentials::OAuthBearer { token: username } => username,
Credentials::Plain { username, .. } | Credentials::XOauth2 { username, .. } => {
username.as_str().into()
}
Credentials::OAuthBearer { .. } => None,
}
}
}
11 changes: 9 additions & 2 deletions crates/common/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,20 @@ impl Core {
})
.unwrap_or_default();

#[cfg(not(feature = "enterprise"))]
let is_enterprise = false;

// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <[email protected]>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let enterprise = crate::enterprise::Enterprise::parse(config, &stores, &data).await;

#[cfg(feature = "enterprise")]
if enterprise.is_none() {
let is_enterprise = enterprise.is_some();

#[cfg(feature = "enterprise")]
if is_enterprise {
if data.is_enterprise_store() {
config
.new_build_error("storage.data", "SQL read replicas is an Enterprise feature");
Expand Down Expand Up @@ -121,7 +127,8 @@ impl Core {
}
})
.unwrap_or_default();
let mut directories = Directories::parse(config, &stores, data.clone()).await;
let mut directories =
Directories::parse(config, &stores, data.clone(), is_enterprise).await;
let directory = config
.value_require("storage.directory")
.map(|id| id.to_string())
Expand Down
14 changes: 8 additions & 6 deletions crates/common/src/listener/blocked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,21 @@ impl Server {
Ok(false)
}

pub async fn is_auth_fail2banned(&self, ip: IpAddr, login: &str) -> trc::Result<bool> {
pub async fn is_auth_fail2banned(&self, ip: IpAddr, login: Option<&str>) -> trc::Result<bool> {
if let Some(rate) = &self.core.network.security.auth_fail_rate {
let login = login.unwrap_or_default();
let is_allowed = self.is_ip_allowed(&ip)
|| (self
.lookup_store()
.is_rate_allowed(format!("b:{ip}").as_bytes(), rate, false)
.await?
.is_none()
&& self
.lookup_store()
.is_rate_allowed(format!("b:{login}").as_bytes(), rate, false)
.await?
.is_none());
&& (login.is_empty()
|| self
.lookup_store()
.is_rate_allowed(format!("b:{login}").as_bytes(), rate, false)
.await?
.is_none()));
if !is_allowed {
return self.block_ip(ip).await.map(|_| true);
}
Expand Down
5 changes: 4 additions & 1 deletion crates/directory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "directory"
version = "0.10.1"
version = "0.10.2"
edition = "2021"
resolver = "2"

Expand Down Expand Up @@ -36,6 +36,9 @@ futures = "0.3"
regex = "1.7.0"
serde = { version = "1.0", features = ["derive"]}
totp-rs = { version = "5.5.1", features = ["otpauth"] }
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"] }
serde_json = "1.0"
base64 = "0.22"

[dev-dependencies]
tokio = { version = "1.23", features = ["full"] }
Expand Down
Loading

0 comments on commit 1d9ac04

Please sign in to comment.