You've already forked lldap
mirror of
https://github.com/lldap/lldap.git
synced 2026-04-06 13:02:57 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2d9d47623 | |||
| edf22afda0 | |||
| 7e64e061d3 | |||
| 233262efa6 | |||
| b8b48ebe24 | |||
| 8a8eb4157c | |||
| 1c92ae60d3 | |||
| f7ab6ded36 | |||
| a90695a6ce | |||
| df49d827d0 |
Generated
+1
@@ -2729,6 +2729,7 @@ dependencies = [
|
||||
"lldap_domain_handlers",
|
||||
"lldap_domain_model",
|
||||
"lldap_ldap",
|
||||
"lldap_opaque_handler",
|
||||
"lldap_sql_backend_handler",
|
||||
"lldap_test_utils",
|
||||
"lldap_validation",
|
||||
|
||||
@@ -55,21 +55,25 @@ version = "1"
|
||||
mockall = "0.11.4"
|
||||
pretty_assertions = "1"
|
||||
|
||||
#[dev-dependencies.lldap_auth]
|
||||
#path = "../auth"
|
||||
#features = ["test"]
|
||||
#
|
||||
#[dev-dependencies.lldap_opaque_handler]
|
||||
#path = "../opaque-handler"
|
||||
#features = ["test"]
|
||||
[dev-dependencies.lldap_auth]
|
||||
path = "../auth"
|
||||
features = ["test"]
|
||||
|
||||
[dev-dependencies.lldap_domain]
|
||||
path = "../domain"
|
||||
features = ["test"]
|
||||
|
||||
[dev-dependencies.lldap_opaque_handler]
|
||||
path = "../opaque-handler"
|
||||
features = ["test"]
|
||||
|
||||
[dev-dependencies.lldap_test_utils]
|
||||
path = "../test-utils"
|
||||
#
|
||||
#[dev-dependencies.lldap_sql_backend_handler]
|
||||
#path = "../sql-backend-handler"
|
||||
#features = ["test"]
|
||||
|
||||
[dev-dependencies.lldap_sql_backend_handler]
|
||||
path = "../sql-backend-handler"
|
||||
features = ["test"]
|
||||
|
||||
[dev-dependencies.tokio]
|
||||
features = ["full"]
|
||||
version = "1.25"
|
||||
version = "1.25"
|
||||
|
||||
@@ -291,12 +291,12 @@ pub fn make_ldap_subschema_entry(schema: PublicSchema) -> LdapOp {
|
||||
}
|
||||
|
||||
pub(crate) fn is_root_dse_request(request: &LdapSearchRequest) -> bool {
|
||||
if request.base.is_empty() && request.scope == LdapSearchScope::Base {
|
||||
if let LdapFilter::Present(attribute) = &request.filter {
|
||||
if attribute.eq_ignore_ascii_case("objectclass") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if request.base.is_empty()
|
||||
&& request.scope == LdapSearchScope::Base
|
||||
&& let LdapFilter::Present(attribute) = &request.filter
|
||||
&& attribute.eq_ignore_ascii_case("objectclass")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1159,6 +1159,30 @@ async fn migrate_to_v11(transaction: DatabaseTransaction) -> Result<DatabaseTran
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Initialize existing users with modified_date and password_modified_date = now
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
transaction
|
||||
.execute(
|
||||
builder.build(
|
||||
Query::update()
|
||||
.table(Users::Table)
|
||||
.value(Users::ModifiedDate, now)
|
||||
.value(Users::PasswordModifiedDate, now),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Initialize existing groups with modified_date = now
|
||||
transaction
|
||||
.execute(
|
||||
builder.build(
|
||||
Query::update()
|
||||
.table(Groups::Table)
|
||||
.value(Groups::ModifiedDate, now),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(transaction)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use base64::Engine;
|
||||
use lldap_auth::opaque;
|
||||
use lldap_domain::types::UserId;
|
||||
use lldap_domain_handlers::handler::{BindRequest, LoginHandler, UserRequestFilter, UserListerBackendHandler};
|
||||
use lldap_domain_handlers::handler::{BindRequest, LoginHandler};
|
||||
use lldap_domain_model::{
|
||||
error::{DomainError, Result},
|
||||
model::{self, UserColumn},
|
||||
@@ -60,26 +60,6 @@ impl SqlBackendHandler {
|
||||
.await?
|
||||
.and_then(|u| u.0))
|
||||
}
|
||||
|
||||
#[instrument(skip(self), level = "debug", err)]
|
||||
async fn find_user_id_by_email(&self, email: &str) -> Result<Option<UserId>> {
|
||||
// Find user ID by email address
|
||||
let users = self
|
||||
.list_users(
|
||||
Some(UserRequestFilter::Equality(UserColumn::Email, email.to_owned())),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if users.len() > 1 {
|
||||
warn!("Multiple users found with email '{}', login ambiguous", email);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(users.first().map(|user_and_groups| user_and_groups.user.user_id.clone()))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -121,33 +101,14 @@ impl OpaqueHandler for SqlOpaqueHandler {
|
||||
&self,
|
||||
request: login::ClientLoginStartRequest,
|
||||
) -> Result<login::ServerLoginStartResponse> {
|
||||
// First try to authenticate with the provided name as a user ID
|
||||
let mut actual_user_id = request.username.clone();
|
||||
let mut maybe_password_file = self
|
||||
.get_password_file_for_user(request.username.clone())
|
||||
.await?;
|
||||
|
||||
// If no user found by user ID, try to find by email for web UI login
|
||||
if maybe_password_file.is_none() {
|
||||
debug!(r#"User "{}" not found by user ID, trying email lookup for web login"#, &request.username);
|
||||
if let Some(user_id_by_email) = self
|
||||
.find_user_id_by_email(request.username.as_str())
|
||||
.await?
|
||||
{
|
||||
debug!(r#"Found user by email: "{}""#, &user_id_by_email);
|
||||
actual_user_id = user_id_by_email;
|
||||
maybe_password_file = self
|
||||
.get_password_file_for_user(actual_user_id.clone())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
info!(r#"OPAQUE login attempt for "{}" (input: "{}")"#, &actual_user_id, &request.username);
|
||||
|
||||
let maybe_password_file = maybe_password_file
|
||||
let user_id = request.username;
|
||||
info!(r#"OPAQUE login attempt for "{}""#, &user_id);
|
||||
let maybe_password_file = self
|
||||
.get_password_file_for_user(user_id.clone())
|
||||
.await?
|
||||
.map(|bytes| {
|
||||
opaque::server::ServerRegistration::deserialize(&bytes).map_err(|_| {
|
||||
DomainError::InternalError(format!("Corrupted password file for {}", &actual_user_id))
|
||||
DomainError::InternalError(format!("Corrupted password file for {}", &user_id))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
@@ -159,11 +120,11 @@ impl OpaqueHandler for SqlOpaqueHandler {
|
||||
&self.opaque_setup,
|
||||
maybe_password_file,
|
||||
request.login_start_request,
|
||||
&actual_user_id,
|
||||
&user_id,
|
||||
)?;
|
||||
let secret_key = self.get_orion_secret_key()?;
|
||||
let server_data = login::ServerData {
|
||||
username: actual_user_id,
|
||||
username: user_id,
|
||||
server_login: start_response.state,
|
||||
};
|
||||
let encrypted_state = orion::aead::seal(&secret_key, &bincode::serialize(&server_data)?)?;
|
||||
@@ -345,7 +306,6 @@ mod tests {
|
||||
let handler = SqlOpaqueHandler::new(generate_random_private_key(), sql_pool.clone());
|
||||
insert_user(&handler, "bob", "bob00").await;
|
||||
|
||||
// Test login with username (should work)
|
||||
handler
|
||||
.bind(BindRequest {
|
||||
name: UserId::new("bob"),
|
||||
@@ -353,8 +313,6 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Test login with non-existent user
|
||||
handler
|
||||
.bind(BindRequest {
|
||||
name: UserId::new("andrew"),
|
||||
@@ -362,8 +320,6 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Test login with wrong password
|
||||
handler
|
||||
.bind(BindRequest {
|
||||
name: UserId::new("bob"),
|
||||
@@ -371,39 +327,6 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Test that email login is NOT supported for LDAP bind
|
||||
handler
|
||||
.bind(BindRequest {
|
||||
name: UserId::new("bob@bob.bob"),
|
||||
password: "bob00".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_opaque_login_with_email() {
|
||||
let sql_pool = get_initialized_db().await;
|
||||
crate::logging::init_for_tests();
|
||||
let backend_handler = SqlBackendHandler::new(generate_random_private_key(), sql_pool);
|
||||
insert_user(&backend_handler, "bob", "bob00").await;
|
||||
|
||||
// Test OPAQUE login with username (should work as before)
|
||||
attempt_login(&backend_handler, "bob", "bob00").await.unwrap();
|
||||
|
||||
// Test OPAQUE login with email (new functionality)
|
||||
attempt_login(&backend_handler, "bob@bob.bob", "bob00").await.unwrap();
|
||||
|
||||
// Test OPAQUE login with non-existent email
|
||||
attempt_login(&backend_handler, "nonexistent@bob.bob", "bob00")
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Test OPAQUE login with wrong password using email
|
||||
attempt_login(&backend_handler, "bob@bob.bob", "wrong_password")
|
||||
.await
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -395,12 +395,12 @@ impl UserBackendHandler for SqlBackendHandler {
|
||||
|
||||
#[instrument(skip_all, level = "debug", err, fields(user_id = ?user_id.as_str(), group_id))]
|
||||
async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()> {
|
||||
let user_id = user_id.clone();
|
||||
let user_id_owned = user_id.clone();
|
||||
self.sql_pool
|
||||
.transaction::<_, _, sea_orm::DbErr>(|transaction| {
|
||||
Box::pin(async move {
|
||||
let new_membership = model::memberships::ActiveModel {
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
user_id: ActiveValue::Set(user_id_owned),
|
||||
group_id: ActiveValue::Set(group_id),
|
||||
};
|
||||
new_membership.insert(transaction).await?;
|
||||
@@ -423,16 +423,16 @@ impl UserBackendHandler for SqlBackendHandler {
|
||||
|
||||
#[instrument(skip_all, level = "debug", err, fields(user_id = ?user_id.as_str(), group_id))]
|
||||
async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()> {
|
||||
let user_id = user_id.clone();
|
||||
let user_id_owned = user_id.clone();
|
||||
self.sql_pool
|
||||
.transaction::<_, _, sea_orm::DbErr>(|transaction| {
|
||||
Box::pin(async move {
|
||||
let res = model::Membership::delete_by_id((user_id.clone(), group_id))
|
||||
let res = model::Membership::delete_by_id((user_id_owned.clone(), group_id))
|
||||
.exec(transaction)
|
||||
.await?;
|
||||
if res.rows_affected == 0 {
|
||||
return Err(sea_orm::DbErr::Custom(format!(
|
||||
"No such membership: '{user_id}' -> {group_id:?}"
|
||||
"No such membership: '{user_id_owned}' -> {group_id:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
+6
-14
@@ -125,7 +125,7 @@ async fn setup_sql_tables(database_url: &DatabaseUrl) -> Result<DatabaseConnecti
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn set_up_server(config: Configuration) -> Result<(ServerBuilder, DatabaseConnection)> {
|
||||
async fn set_up_server(config: Configuration) -> Result<ServerBuilder> {
|
||||
info!("Starting LLDAP version {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let sql_pool = setup_sql_tables(&config.database_url).await?;
|
||||
@@ -214,9 +214,9 @@ async fn set_up_server(config: Configuration) -> Result<(ServerBuilder, Database
|
||||
.await
|
||||
.context("while binding the TCP server")?;
|
||||
// Run every hour.
|
||||
let scheduler = Scheduler::new("0 0 * * * * *", sql_pool.clone());
|
||||
let scheduler = Scheduler::new("0 0 * * * * *", sql_pool);
|
||||
scheduler.start();
|
||||
Ok((server_builder, sql_pool))
|
||||
Ok(server_builder)
|
||||
}
|
||||
|
||||
async fn run_server_command(opts: RunOpts) -> Result<()> {
|
||||
@@ -225,14 +225,9 @@ async fn run_server_command(opts: RunOpts) -> Result<()> {
|
||||
let config = configuration::init(opts)?;
|
||||
logging::init(&config)?;
|
||||
|
||||
let (server, sql_pool) = set_up_server(config).await?;
|
||||
let server = server.workers(1);
|
||||
let server = set_up_server(config).await?.workers(1);
|
||||
|
||||
let result = server.run().await.context("while starting the server");
|
||||
if let Err(e) = sql_pool.close().await {
|
||||
error!("Error closing database connection pool: {}", e);
|
||||
}
|
||||
result
|
||||
server.run().await.context("while starting the server")
|
||||
}
|
||||
|
||||
async fn send_test_email_command(opts: TestEmailOpts) -> Result<()> {
|
||||
@@ -280,11 +275,8 @@ async fn create_schema_command(opts: RunOpts) -> Result<()> {
|
||||
debug!("CLI: {:#?}", &opts);
|
||||
let config = configuration::init(opts)?;
|
||||
logging::init(&config)?;
|
||||
let sql_pool = setup_sql_tables(&config.database_url).await?;
|
||||
setup_sql_tables(&config.database_url).await?;
|
||||
info!("Schema created successfully.");
|
||||
if let Err(e) = sql_pool.close().await {
|
||||
error!("Error closing database connection pool: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user