mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	Add a handler for OPAQUE messages
This commit is contained in:
		
							parent
							
								
									fdc4249c30
								
							
						
					
					
						commit
						226a899dfe
					
				@ -8,6 +8,8 @@ pub enum Error {
 | 
				
			|||||||
    DatabaseError(#[from] sqlx::Error),
 | 
					    DatabaseError(#[from] sqlx::Error),
 | 
				
			||||||
    #[error("Authentication protocol error for `{0}`")]
 | 
					    #[error("Authentication protocol error for `{0}`")]
 | 
				
			||||||
    AuthenticationProtocolError(#[from] lldap_model::opaque::AuthenticationError),
 | 
					    AuthenticationProtocolError(#[from] lldap_model::opaque::AuthenticationError),
 | 
				
			||||||
 | 
					    #[error("Internal error: `{0}`")]
 | 
				
			||||||
 | 
					    InternalError(String),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type Result<T> = std::result::Result<T, Error>;
 | 
					pub type Result<T> = std::result::Result<T, Error>;
 | 
				
			||||||
 | 
				
			|||||||
@ -5,8 +5,12 @@ use std::collections::HashSet;
 | 
				
			|||||||
pub use lldap_model::*;
 | 
					pub use lldap_model::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
pub trait BackendHandler: Clone + Send {
 | 
					pub trait LoginHandler: Clone + Send {
 | 
				
			||||||
    async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
					    async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					pub trait BackendHandler: Clone + Send {
 | 
				
			||||||
    async fn list_users(&self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
					    async fn list_users(&self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
				
			||||||
    async fn list_groups(&self) -> Result<Vec<Group>>;
 | 
					    async fn list_groups(&self) -> Result<Vec<Group>>;
 | 
				
			||||||
    async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
					    async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
				
			||||||
@ -24,7 +28,6 @@ mockall::mock! {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    #[async_trait]
 | 
					    #[async_trait]
 | 
				
			||||||
    impl BackendHandler for TestBackendHandler {
 | 
					    impl BackendHandler for TestBackendHandler {
 | 
				
			||||||
        async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
					 | 
				
			||||||
        async fn list_users(&self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
					        async fn list_users(&self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
				
			||||||
        async fn list_groups(&self) -> Result<Vec<Group>>;
 | 
					        async fn list_groups(&self) -> Result<Vec<Group>>;
 | 
				
			||||||
        async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
					        async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
				
			||||||
@ -33,4 +36,8 @@ mockall::mock! {
 | 
				
			|||||||
        async fn get_user_groups(&self, user: String) -> Result<HashSet<String>>;
 | 
					        async fn get_user_groups(&self, user: String) -> Result<HashSet<String>>;
 | 
				
			||||||
        async fn add_user_to_group(&self, request: AddUserToGroupRequest) -> Result<()>;
 | 
					        async fn add_user_to_group(&self, request: AddUserToGroupRequest) -> Result<()>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    #[async_trait]
 | 
				
			||||||
 | 
					    impl LoginHandler for TestBackendHandler {
 | 
				
			||||||
 | 
					        async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,6 @@
 | 
				
			|||||||
pub mod error;
 | 
					pub mod error;
 | 
				
			||||||
pub mod handler;
 | 
					pub mod handler;
 | 
				
			||||||
 | 
					pub mod opaque_handler;
 | 
				
			||||||
pub mod sql_backend_handler;
 | 
					pub mod sql_backend_handler;
 | 
				
			||||||
 | 
					pub mod sql_opaque_handler;
 | 
				
			||||||
pub mod sql_tables;
 | 
					pub mod sql_tables;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										36
									
								
								src/domain/opaque_handler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/domain/opaque_handler.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					use super::error::*;
 | 
				
			||||||
 | 
					use async_trait::async_trait;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use lldap_model::{login, registration};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					pub trait OpaqueHandler: Clone + Send {
 | 
				
			||||||
 | 
					    async fn login_start(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: login::ClientLoginStartRequest,
 | 
				
			||||||
 | 
					    ) -> Result<login::ServerLoginStartResponse>;
 | 
				
			||||||
 | 
					    async fn login_finish(&self, request: login::ClientLoginFinishRequest) -> Result<String>;
 | 
				
			||||||
 | 
					    async fn registration_start(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: registration::ClientRegistrationStartRequest,
 | 
				
			||||||
 | 
					    ) -> Result<registration::ServerRegistrationStartResponse>;
 | 
				
			||||||
 | 
					    async fn registration_finish(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: registration::ClientRegistrationFinishRequest,
 | 
				
			||||||
 | 
					    ) -> Result<()>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mockall::mock! {
 | 
				
			||||||
 | 
					    pub TestOpaqueHandler{}
 | 
				
			||||||
 | 
					    impl Clone for TestOpaqueHandler {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #[async_trait]
 | 
				
			||||||
 | 
					    impl OpaqueHandler for TestOpaqueHandler {
 | 
				
			||||||
 | 
					    async fn login_start(&self, request: login::ClientLoginStartRequest) -> Result<login::ServerLoginStartResponse>;
 | 
				
			||||||
 | 
					    async fn login_finish(&self, request: login::ClientLoginFinishRequest ) -> Result<String>;
 | 
				
			||||||
 | 
					    async fn registration_start(&self, request: registration::ClientRegistrationStartRequest) -> Result<registration::ServerRegistrationStartResponse>;
 | 
				
			||||||
 | 
					    async fn registration_finish(&self, request: registration::ClientRegistrationFinishRequest ) -> Result<()>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -4,7 +4,6 @@ use async_trait::async_trait;
 | 
				
			|||||||
use futures_util::StreamExt;
 | 
					use futures_util::StreamExt;
 | 
				
			||||||
use futures_util::TryStreamExt;
 | 
					use futures_util::TryStreamExt;
 | 
				
			||||||
use lldap_model::opaque;
 | 
					use lldap_model::opaque;
 | 
				
			||||||
use log::*;
 | 
					 | 
				
			||||||
use sea_query::{Expr, Iden, Order, Query, SimpleExpr, Value};
 | 
					use sea_query::{Expr, Iden, Order, Query, SimpleExpr, Value};
 | 
				
			||||||
use sqlx::Row;
 | 
					use sqlx::Row;
 | 
				
			||||||
use std::collections::HashSet;
 | 
					use std::collections::HashSet;
 | 
				
			||||||
@ -21,7 +20,7 @@ impl SqlBackendHandler {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_password_file(
 | 
					pub fn get_password_file(
 | 
				
			||||||
    clear_password: &str,
 | 
					    clear_password: &str,
 | 
				
			||||||
    server_public_key: &opaque::PublicKey,
 | 
					    server_public_key: &opaque::PublicKey,
 | 
				
			||||||
) -> Result<opaque::server::ServerRegistration> {
 | 
					) -> Result<opaque::server::ServerRegistration> {
 | 
				
			||||||
@ -48,30 +47,6 @@ fn get_password_file(
 | 
				
			|||||||
    )?)
 | 
					    )?)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn passwords_match(
 | 
					 | 
				
			||||||
    password_file_bytes: &[u8],
 | 
					 | 
				
			||||||
    clear_password: &str,
 | 
					 | 
				
			||||||
    server_private_key: &opaque::PrivateKey,
 | 
					 | 
				
			||||||
) -> Result<()> {
 | 
					 | 
				
			||||||
    use opaque::{client, server};
 | 
					 | 
				
			||||||
    let mut rng = rand::rngs::OsRng;
 | 
					 | 
				
			||||||
    let client_login_start_result = client::login::start_login(clear_password, &mut rng)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let password_file = server::ServerRegistration::deserialize(password_file_bytes)
 | 
					 | 
				
			||||||
        .map_err(opaque::AuthenticationError::ProtocolError)?;
 | 
					 | 
				
			||||||
    let server_login_start_result = server::login::start_login(
 | 
					 | 
				
			||||||
        &mut rng,
 | 
					 | 
				
			||||||
        password_file,
 | 
					 | 
				
			||||||
        server_private_key,
 | 
					 | 
				
			||||||
        client_login_start_result.message,
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
    client::login::finish_login(
 | 
					 | 
				
			||||||
        client_login_start_result.state,
 | 
					 | 
				
			||||||
        server_login_start_result.message,
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn get_filter_expr(filter: RequestFilter) -> SimpleExpr {
 | 
					fn get_filter_expr(filter: RequestFilter) -> SimpleExpr {
 | 
				
			||||||
    use RequestFilter::*;
 | 
					    use RequestFilter::*;
 | 
				
			||||||
    fn get_repeated_filter(
 | 
					    fn get_repeated_filter(
 | 
				
			||||||
@ -95,42 +70,6 @@ fn get_filter_expr(filter: RequestFilter) -> SimpleExpr {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
impl BackendHandler for SqlBackendHandler {
 | 
					impl BackendHandler for SqlBackendHandler {
 | 
				
			||||||
    async fn bind(&self, request: BindRequest) -> Result<()> {
 | 
					 | 
				
			||||||
        if request.name == self.config.ldap_user_dn {
 | 
					 | 
				
			||||||
            if request.password == self.config.ldap_user_pass {
 | 
					 | 
				
			||||||
                return Ok(());
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                debug!(r#"Invalid password for LDAP bind user"#);
 | 
					 | 
				
			||||||
                return Err(Error::AuthenticationError(request.name));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let query = Query::select()
 | 
					 | 
				
			||||||
            .column(Users::PasswordHash)
 | 
					 | 
				
			||||||
            .from(Users::Table)
 | 
					 | 
				
			||||||
            .and_where(Expr::col(Users::UserId).eq(request.name.as_str()))
 | 
					 | 
				
			||||||
            .to_string(DbQueryBuilder {});
 | 
					 | 
				
			||||||
        if let Ok(row) = sqlx::query(&query).fetch_one(&self.sql_pool).await {
 | 
					 | 
				
			||||||
            if let Some(password_hash) =
 | 
					 | 
				
			||||||
                row.get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if let Err(e) = passwords_match(
 | 
					 | 
				
			||||||
                    &&password_hash,
 | 
					 | 
				
			||||||
                    &request.password,
 | 
					 | 
				
			||||||
                    self.config.get_server_keys().private(),
 | 
					 | 
				
			||||||
                ) {
 | 
					 | 
				
			||||||
                    debug!(r#"Invalid password for "{}": {}"#, request.name, e);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    return Ok(());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                debug!(r#"User "{}" has no password"#, request.name);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            debug!(r#"No user found for "{}""#, request.name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Err(Error::AuthenticationError(request.name))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn list_users(&self, request: ListUsersRequest) -> Result<Vec<User>> {
 | 
					    async fn list_users(&self, request: ListUsersRequest) -> Result<Vec<User>> {
 | 
				
			||||||
        let query = {
 | 
					        let query = {
 | 
				
			||||||
            let mut query_builder = Query::select()
 | 
					            let mut query_builder = Query::select()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										396
									
								
								src/domain/sql_opaque_handler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								src/domain/sql_opaque_handler.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,396 @@
 | 
				
			|||||||
 | 
					use super::{
 | 
				
			||||||
 | 
					    error::*, handler::LoginHandler, opaque_handler::*, sql_backend_handler::SqlBackendHandler,
 | 
				
			||||||
 | 
					    sql_tables::*,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use async_trait::async_trait;
 | 
				
			||||||
 | 
					use lldap_model::{opaque, BindRequest};
 | 
				
			||||||
 | 
					use log::*;
 | 
				
			||||||
 | 
					use rand::{CryptoRng, RngCore};
 | 
				
			||||||
 | 
					use sea_query::{Expr, Iden, Query};
 | 
				
			||||||
 | 
					use sqlx::Row;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SqlOpaqueHandler = SqlBackendHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn generate_random_id<R: RngCore + CryptoRng>(rng: &mut R) -> String {
 | 
				
			||||||
 | 
					    use rand::{distributions::Alphanumeric, Rng};
 | 
				
			||||||
 | 
					    std::iter::repeat(())
 | 
				
			||||||
 | 
					        .map(|()| rng.sample(Alphanumeric))
 | 
				
			||||||
 | 
					        .map(char::from)
 | 
				
			||||||
 | 
					        .take(32)
 | 
				
			||||||
 | 
					        .collect()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn passwords_match(
 | 
				
			||||||
 | 
					    password_file_bytes: &[u8],
 | 
				
			||||||
 | 
					    clear_password: &str,
 | 
				
			||||||
 | 
					    server_private_key: &opaque::PrivateKey,
 | 
				
			||||||
 | 
					) -> Result<()> {
 | 
				
			||||||
 | 
					    use opaque::{client, server};
 | 
				
			||||||
 | 
					    let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
 | 
					    let client_login_start_result = client::login::start_login(clear_password, &mut rng)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let password_file = server::ServerRegistration::deserialize(password_file_bytes)
 | 
				
			||||||
 | 
					        .map_err(opaque::AuthenticationError::ProtocolError)?;
 | 
				
			||||||
 | 
					    let server_login_start_result = server::login::start_login(
 | 
				
			||||||
 | 
					        &mut rng,
 | 
				
			||||||
 | 
					        password_file,
 | 
				
			||||||
 | 
					        server_private_key,
 | 
				
			||||||
 | 
					        client_login_start_result.message,
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
 | 
					    client::login::finish_login(
 | 
				
			||||||
 | 
					        client_login_start_result.state,
 | 
				
			||||||
 | 
					        server_login_start_result.message,
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl LoginHandler for SqlBackendHandler {
 | 
				
			||||||
 | 
					    async fn bind(&self, request: BindRequest) -> Result<()> {
 | 
				
			||||||
 | 
					        if request.name == self.config.ldap_user_dn {
 | 
				
			||||||
 | 
					            if request.password == self.config.ldap_user_pass {
 | 
				
			||||||
 | 
					                return Ok(());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                debug!(r#"Invalid password for LDAP bind user"#);
 | 
				
			||||||
 | 
					                return Err(Error::AuthenticationError(request.name));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let query = Query::select()
 | 
				
			||||||
 | 
					            .column(Users::PasswordHash)
 | 
				
			||||||
 | 
					            .from(Users::Table)
 | 
				
			||||||
 | 
					            .and_where(Expr::col(Users::UserId).eq(request.name.as_str()))
 | 
				
			||||||
 | 
					            .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					        if let Ok(row) = sqlx::query(&query).fetch_one(&self.sql_pool).await {
 | 
				
			||||||
 | 
					            if let Some(password_hash) =
 | 
				
			||||||
 | 
					                row.get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if let Err(e) = passwords_match(
 | 
				
			||||||
 | 
					                    &&password_hash,
 | 
				
			||||||
 | 
					                    &request.password,
 | 
				
			||||||
 | 
					                    self.config.get_server_keys().private(),
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    debug!(r#"Invalid password for "{}": {}"#, request.name, e);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return Ok(());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                debug!(r#"User "{}" has no password"#, request.name);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            debug!(r#"No user found for "{}""#, request.name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(Error::AuthenticationError(request.name))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl OpaqueHandler for SqlOpaqueHandler {
 | 
				
			||||||
 | 
					    async fn login_start(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: login::ClientLoginStartRequest,
 | 
				
			||||||
 | 
					    ) -> Result<login::ServerLoginStartResponse> {
 | 
				
			||||||
 | 
					        // Fetch the previously registered password file from the DB.
 | 
				
			||||||
 | 
					        let password_file_bytes = {
 | 
				
			||||||
 | 
					            let query = Query::select()
 | 
				
			||||||
 | 
					                .column(Users::PasswordHash)
 | 
				
			||||||
 | 
					                .from(Users::Table)
 | 
				
			||||||
 | 
					                .and_where(Expr::col(Users::UserId).eq(request.username.as_str()))
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&query)
 | 
				
			||||||
 | 
					                .fetch_one(&self.sql_pool)
 | 
				
			||||||
 | 
					                .await?
 | 
				
			||||||
 | 
					                .get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
				
			||||||
 | 
					                // If no password, always fail.
 | 
				
			||||||
 | 
					                .ok_or_else(|| Error::AuthenticationError(request.username.clone()))?
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let password_file = opaque::server::ServerRegistration::deserialize(&password_file_bytes)
 | 
				
			||||||
 | 
					            .map_err(|_| {
 | 
				
			||||||
 | 
					            Error::InternalError(format!("Corrupted password file for {}", request.username))
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
 | 
					        let start_response = opaque::server::login::start_login(
 | 
				
			||||||
 | 
					            &mut rng,
 | 
				
			||||||
 | 
					            password_file,
 | 
				
			||||||
 | 
					            self.config.get_server_keys().private(),
 | 
				
			||||||
 | 
					            request.login_start_request,
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        let login_attempt_id = generate_random_id(&mut rng);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Insert the current login attempt in the DB.
 | 
				
			||||||
 | 
					            let query = Query::insert()
 | 
				
			||||||
 | 
					                .into_table(LoginAttempts::Table)
 | 
				
			||||||
 | 
					                .columns(vec![
 | 
				
			||||||
 | 
					                    LoginAttempts::RandomId,
 | 
				
			||||||
 | 
					                    LoginAttempts::UserId,
 | 
				
			||||||
 | 
					                    LoginAttempts::ServerLoginData,
 | 
				
			||||||
 | 
					                    LoginAttempts::Timestamp,
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                .values_panic(vec![
 | 
				
			||||||
 | 
					                    login_attempt_id.as_str().into(),
 | 
				
			||||||
 | 
					                    request.username.as_str().into(),
 | 
				
			||||||
 | 
					                    start_response.state.serialize().into(),
 | 
				
			||||||
 | 
					                    chrono::Utc::now().naive_utc().into(),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&query).execute(&self.sql_pool).await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(login::ServerLoginStartResponse {
 | 
				
			||||||
 | 
					            login_key: login_attempt_id,
 | 
				
			||||||
 | 
					            credential_response: start_response.message,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn login_finish(&self, request: login::ClientLoginFinishRequest) -> Result<String> {
 | 
				
			||||||
 | 
					        // Fetch the previous data from this login attempt.
 | 
				
			||||||
 | 
					        let row = {
 | 
				
			||||||
 | 
					            let query = Query::select()
 | 
				
			||||||
 | 
					                .column(LoginAttempts::UserId)
 | 
				
			||||||
 | 
					                .column(LoginAttempts::ServerLoginData)
 | 
				
			||||||
 | 
					                .from(LoginAttempts::Table)
 | 
				
			||||||
 | 
					                .and_where(Expr::col(LoginAttempts::RandomId).eq(request.login_key.as_str()))
 | 
				
			||||||
 | 
					                .and_where(
 | 
				
			||||||
 | 
					                    Expr::col(LoginAttempts::Timestamp)
 | 
				
			||||||
 | 
					                        .gt(chrono::Utc::now().naive_utc() - chrono::Duration::minutes(5)),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&query).fetch_one(&self.sql_pool).await?
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let username = row.get::<String, _>(&*LoginAttempts::UserId.to_string());
 | 
				
			||||||
 | 
					        let login_data = opaque::server::login::ServerLogin::deserialize(
 | 
				
			||||||
 | 
					            &row.get::<Vec<u8>, _>(&*LoginAttempts::ServerLoginData.to_string()),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .map_err(|_| {
 | 
				
			||||||
 | 
					            Error::InternalError(format!(
 | 
				
			||||||
 | 
					                "Corrupted login data for user `{}` [id `{}`]",
 | 
				
			||||||
 | 
					                username, request.login_key
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					        // Finish the login: this makes sure the client data is correct, and gives a session key we
 | 
				
			||||||
 | 
					        // don't need.
 | 
				
			||||||
 | 
					        let _session_key =
 | 
				
			||||||
 | 
					            opaque::server::login::finish_login(login_data, request.credential_finalization)?
 | 
				
			||||||
 | 
					                .session_key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Login was successful, we can delete the login attempt from the table.
 | 
				
			||||||
 | 
					            let delete_query = Query::delete()
 | 
				
			||||||
 | 
					                .from_table(LoginAttempts::Table)
 | 
				
			||||||
 | 
					                .and_where(Expr::col(LoginAttempts::RandomId).eq(request.login_key))
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&delete_query).execute(&self.sql_pool).await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(username)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn registration_start(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: registration::ClientRegistrationStartRequest,
 | 
				
			||||||
 | 
					    ) -> Result<registration::ServerRegistrationStartResponse> {
 | 
				
			||||||
 | 
					        let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
 | 
					        // Generate the server-side key and derive the data to send back.
 | 
				
			||||||
 | 
					        let start_response = opaque::server::registration::start_registration(
 | 
				
			||||||
 | 
					            &mut rng,
 | 
				
			||||||
 | 
					            request.registration_start_request,
 | 
				
			||||||
 | 
					            self.config.get_server_keys().public(),
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        // Unique ID to identify the registration attempt.
 | 
				
			||||||
 | 
					        let registration_attempt_id = generate_random_id(&mut rng);
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Write the registration attempt to the DB for the later turn.
 | 
				
			||||||
 | 
					            let query = Query::insert()
 | 
				
			||||||
 | 
					                .into_table(RegistrationAttempts::Table)
 | 
				
			||||||
 | 
					                .columns(vec![
 | 
				
			||||||
 | 
					                    RegistrationAttempts::RandomId,
 | 
				
			||||||
 | 
					                    RegistrationAttempts::UserId,
 | 
				
			||||||
 | 
					                    RegistrationAttempts::ServerRegistrationData,
 | 
				
			||||||
 | 
					                    RegistrationAttempts::Timestamp,
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                .values_panic(vec![
 | 
				
			||||||
 | 
					                    registration_attempt_id.as_str().into(),
 | 
				
			||||||
 | 
					                    request.username.as_str().into(),
 | 
				
			||||||
 | 
					                    start_response.state.serialize().into(),
 | 
				
			||||||
 | 
					                    chrono::Utc::now().naive_utc().into(),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&query).execute(&self.sql_pool).await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(registration::ServerRegistrationStartResponse {
 | 
				
			||||||
 | 
					            registration_key: registration_attempt_id,
 | 
				
			||||||
 | 
					            registration_response: start_response.message,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn registration_finish(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: registration::ClientRegistrationFinishRequest,
 | 
				
			||||||
 | 
					    ) -> Result<()> {
 | 
				
			||||||
 | 
					        // Fetch the previous state.
 | 
				
			||||||
 | 
					        let row = {
 | 
				
			||||||
 | 
					            let query = Query::select()
 | 
				
			||||||
 | 
					                .column(RegistrationAttempts::UserId)
 | 
				
			||||||
 | 
					                .column(RegistrationAttempts::ServerRegistrationData)
 | 
				
			||||||
 | 
					                .from(RegistrationAttempts::Table)
 | 
				
			||||||
 | 
					                .and_where(
 | 
				
			||||||
 | 
					                    Expr::col(RegistrationAttempts::RandomId).eq(request.registration_key.as_str()),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .and_where(
 | 
				
			||||||
 | 
					                    Expr::col(RegistrationAttempts::Timestamp)
 | 
				
			||||||
 | 
					                        .gt(chrono::Utc::now().naive_utc() - chrono::Duration::minutes(5)),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&query).fetch_one(&self.sql_pool).await?
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let username = row.get::<String, _>(&*RegistrationAttempts::UserId.to_string());
 | 
				
			||||||
 | 
					        let registration_data = opaque::server::registration::ServerRegistration::deserialize(
 | 
				
			||||||
 | 
					            &row.get::<Vec<u8>, _>(&*RegistrationAttempts::ServerRegistrationData.to_string()),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .map_err(|_| {
 | 
				
			||||||
 | 
					            Error::InternalError(format!(
 | 
				
			||||||
 | 
					                "Corrupted registration data for user `{}` [id `{}`]",
 | 
				
			||||||
 | 
					                username, request.registration_key
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let password_file = opaque::server::registration::get_password_file(
 | 
				
			||||||
 | 
					            registration_data,
 | 
				
			||||||
 | 
					            request.registration_upload,
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Set the user password to the new password.
 | 
				
			||||||
 | 
					            let update_query = Query::update()
 | 
				
			||||||
 | 
					                .table(Users::Table)
 | 
				
			||||||
 | 
					                .values(vec![(
 | 
				
			||||||
 | 
					                    Users::PasswordHash,
 | 
				
			||||||
 | 
					                    password_file.serialize().into(),
 | 
				
			||||||
 | 
					                )])
 | 
				
			||||||
 | 
					                .and_where(Expr::col(Users::UserId).eq(username))
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&update_query).execute(&self.sql_pool).await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Delete the registration attempt.
 | 
				
			||||||
 | 
					            let delete_query = Query::delete()
 | 
				
			||||||
 | 
					                .from_table(RegistrationAttempts::Table)
 | 
				
			||||||
 | 
					                .and_where(Expr::col(RegistrationAttempts::RandomId).eq(request.registration_key))
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {});
 | 
				
			||||||
 | 
					            sqlx::query(&delete_query).execute(&self.sql_pool).await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					    use crate::{
 | 
				
			||||||
 | 
					        domain::{
 | 
				
			||||||
 | 
					            handler::BackendHandler, sql_backend_handler::SqlBackendHandler, sql_tables::init_table,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        infra::configuration::{Configuration, ConfigurationBuilder},
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    use lldap_model::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_default_config() -> Configuration {
 | 
				
			||||||
 | 
					        ConfigurationBuilder::default()
 | 
				
			||||||
 | 
					            .verbose(true)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					            .unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn get_in_memory_db() -> Pool {
 | 
				
			||||||
 | 
					        PoolOptions::new().connect("sqlite::memory:").await.unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn get_initialized_db() -> Pool {
 | 
				
			||||||
 | 
					        let sql_pool = get_in_memory_db().await;
 | 
				
			||||||
 | 
					        init_table(&sql_pool).await.unwrap();
 | 
				
			||||||
 | 
					        sql_pool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn insert_user_no_password(handler: &SqlBackendHandler, name: &str) {
 | 
				
			||||||
 | 
					        handler
 | 
				
			||||||
 | 
					            .create_user(CreateUserRequest {
 | 
				
			||||||
 | 
					                user_id: name.to_string(),
 | 
				
			||||||
 | 
					                email: "bob@bob.bob".to_string(),
 | 
				
			||||||
 | 
					                ..Default::default()
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn attempt_login(
 | 
				
			||||||
 | 
					        opaque_handler: &SqlOpaqueHandler,
 | 
				
			||||||
 | 
					        username: &str,
 | 
				
			||||||
 | 
					        password: &str,
 | 
				
			||||||
 | 
					    ) -> Result<()> {
 | 
				
			||||||
 | 
					        let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
 | 
					        use login::*;
 | 
				
			||||||
 | 
					        let login_start = opaque::client::login::start_login(password, &mut rng)?;
 | 
				
			||||||
 | 
					        let start_response = opaque_handler
 | 
				
			||||||
 | 
					            .login_start(ClientLoginStartRequest {
 | 
				
			||||||
 | 
					                username: username.to_string(),
 | 
				
			||||||
 | 
					                login_start_request: login_start.message,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        let login_finish = opaque::client::login::finish_login(
 | 
				
			||||||
 | 
					            login_start.state,
 | 
				
			||||||
 | 
					            start_response.credential_response,
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        opaque_handler
 | 
				
			||||||
 | 
					            .login_finish(ClientLoginFinishRequest {
 | 
				
			||||||
 | 
					                login_key: start_response.login_key,
 | 
				
			||||||
 | 
					                credential_finalization: login_finish.message,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn attempt_registration(
 | 
				
			||||||
 | 
					        opaque_handler: &SqlOpaqueHandler,
 | 
				
			||||||
 | 
					        username: &str,
 | 
				
			||||||
 | 
					        password: &str,
 | 
				
			||||||
 | 
					    ) -> Result<()> {
 | 
				
			||||||
 | 
					        let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
 | 
					        use registration::*;
 | 
				
			||||||
 | 
					        let registration_start =
 | 
				
			||||||
 | 
					            opaque::client::registration::start_registration(password, &mut rng)?;
 | 
				
			||||||
 | 
					        let start_response = opaque_handler
 | 
				
			||||||
 | 
					            .registration_start(ClientRegistrationStartRequest {
 | 
				
			||||||
 | 
					                username: username.to_string(),
 | 
				
			||||||
 | 
					                registration_start_request: registration_start.message,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        let registration_finish = opaque::client::registration::finish_registration(
 | 
				
			||||||
 | 
					            registration_start.state,
 | 
				
			||||||
 | 
					            start_response.registration_response,
 | 
				
			||||||
 | 
					            &mut rng,
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        opaque_handler
 | 
				
			||||||
 | 
					            .registration_finish(ClientRegistrationFinishRequest {
 | 
				
			||||||
 | 
					                registration_key: start_response.registration_key,
 | 
				
			||||||
 | 
					                registration_upload: registration_finish.message,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[tokio::test]
 | 
				
			||||||
 | 
					    async fn test_flow() -> Result<()> {
 | 
				
			||||||
 | 
					        let sql_pool = get_initialized_db().await;
 | 
				
			||||||
 | 
					        let config = get_default_config();
 | 
				
			||||||
 | 
					        let backend_handler = SqlBackendHandler::new(config.clone(), sql_pool.clone());
 | 
				
			||||||
 | 
					        let opaque_handler = SqlOpaqueHandler::new(config, sql_pool);
 | 
				
			||||||
 | 
					        insert_user_no_password(&backend_handler, "bob").await;
 | 
				
			||||||
 | 
					        attempt_login(&opaque_handler, "bob", "bob00")
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap_err();
 | 
				
			||||||
 | 
					        attempt_registration(&opaque_handler, "bob", "bob00").await?;
 | 
				
			||||||
 | 
					        attempt_login(&opaque_handler, "bob", "wrong_password")
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap_err();
 | 
				
			||||||
 | 
					        attempt_login(&opaque_handler, "bob", "bob00").await?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,10 +1,14 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::handler::*,
 | 
					    domain::{
 | 
				
			||||||
 | 
					        handler::{BackendHandler, LoginHandler},
 | 
				
			||||||
 | 
					        opaque_handler::OpaqueHandler,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    infra::{
 | 
					    infra::{
 | 
				
			||||||
        tcp_backend_handler::*,
 | 
					        tcp_backend_handler::*,
 | 
				
			||||||
        tcp_server::{error_to_http_response, AppState},
 | 
					        tcp_server::{error_to_http_response, AppState},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					use lldap_model::{JWTClaims, BindRequest};
 | 
				
			||||||
use actix_web::{
 | 
					use actix_web::{
 | 
				
			||||||
    cookie::{Cookie, SameSite},
 | 
					    cookie::{Cookie, SameSite},
 | 
				
			||||||
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
 | 
					    dev::{Service, ServiceRequest, ServiceResponse, Transform},
 | 
				
			||||||
@ -166,7 +170,7 @@ async fn post_authorize<Backend>(
 | 
				
			|||||||
    request: web::Json<BindRequest>,
 | 
					    request: web::Json<BindRequest>,
 | 
				
			||||||
) -> HttpResponse
 | 
					) -> HttpResponse
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    Backend: TcpBackendHandler + BackendHandler + 'static,
 | 
					    Backend: TcpBackendHandler + BackendHandler + LoginHandler + 'static,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    let req: BindRequest = request.clone();
 | 
					    let req: BindRequest = request.clone();
 | 
				
			||||||
    data.backend_handler
 | 
					    data.backend_handler
 | 
				
			||||||
@ -299,7 +303,7 @@ where
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub fn configure_server<Backend>(cfg: &mut web::ServiceConfig)
 | 
					pub fn configure_server<Backend>(cfg: &mut web::ServiceConfig)
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    Backend: TcpBackendHandler + BackendHandler + 'static,
 | 
					    Backend: TcpBackendHandler + LoginHandler + OpaqueHandler + BackendHandler + 'static,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    cfg.service(web::resource("").route(web::post().to(post_authorize::<Backend>)))
 | 
					    cfg.service(web::resource("").route(web::post().to(post_authorize::<Backend>)))
 | 
				
			||||||
        .service(web::resource("/refresh").route(web::get().to(get_refresh::<Backend>)))
 | 
					        .service(web::resource("/refresh").route(web::get().to(get_refresh::<Backend>)))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
use crate::domain::handler::{BackendHandler, ListUsersRequest, RequestFilter, User};
 | 
					use crate::domain::handler::{BackendHandler, ListUsersRequest, LoginHandler, RequestFilter, User};
 | 
				
			||||||
use anyhow::{bail, Result};
 | 
					use anyhow::{bail, Result};
 | 
				
			||||||
use ldap3_server::simple::*;
 | 
					use ldap3_server::simple::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -147,7 +147,7 @@ fn convert_filter(filter: &LdapFilter) -> Result<RequestFilter> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct LdapHandler<Backend: BackendHandler> {
 | 
					pub struct LdapHandler<Backend: BackendHandler + LoginHandler> {
 | 
				
			||||||
    dn: String,
 | 
					    dn: String,
 | 
				
			||||||
    backend_handler: Backend,
 | 
					    backend_handler: Backend,
 | 
				
			||||||
    pub base_dn: Vec<(String, String)>,
 | 
					    pub base_dn: Vec<(String, String)>,
 | 
				
			||||||
@ -155,7 +155,7 @@ pub struct LdapHandler<Backend: BackendHandler> {
 | 
				
			|||||||
    ldap_user_dn: String,
 | 
					    ldap_user_dn: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<Backend: BackendHandler> LdapHandler<Backend> {
 | 
					impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
 | 
				
			||||||
    pub fn new(backend_handler: Backend, ldap_base_dn: String, ldap_user_dn: String) -> Self {
 | 
					    pub fn new(backend_handler: Backend, ldap_base_dn: String, ldap_user_dn: String) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            dn: "Unauthenticated".to_string(),
 | 
					            dn: "Unauthenticated".to_string(),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
use crate::domain::handler::BackendHandler;
 | 
					use crate::domain::handler::{BackendHandler, LoginHandler};
 | 
				
			||||||
use crate::infra::configuration::Configuration;
 | 
					use crate::infra::configuration::Configuration;
 | 
				
			||||||
use crate::infra::ldap_handler::LdapHandler;
 | 
					use crate::infra::ldap_handler::LdapHandler;
 | 
				
			||||||
use actix_rt::net::TcpStream;
 | 
					use actix_rt::net::TcpStream;
 | 
				
			||||||
@ -12,11 +12,14 @@ use log::*;
 | 
				
			|||||||
use tokio::net::tcp::WriteHalf;
 | 
					use tokio::net::tcp::WriteHalf;
 | 
				
			||||||
use tokio_util::codec::{FramedRead, FramedWrite};
 | 
					use tokio_util::codec::{FramedRead, FramedWrite};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn handle_incoming_message<Backend: BackendHandler>(
 | 
					async fn handle_incoming_message<Backend>(
 | 
				
			||||||
    msg: Result<LdapMsg, std::io::Error>,
 | 
					    msg: Result<LdapMsg, std::io::Error>,
 | 
				
			||||||
    resp: &mut FramedWrite<WriteHalf<'_>, LdapCodec>,
 | 
					    resp: &mut FramedWrite<WriteHalf<'_>, LdapCodec>,
 | 
				
			||||||
    session: &mut LdapHandler<Backend>,
 | 
					    session: &mut LdapHandler<Backend>,
 | 
				
			||||||
) -> Result<bool> {
 | 
					) -> Result<bool>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    Backend: BackendHandler + LoginHandler,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
    use futures_util::SinkExt;
 | 
					    use futures_util::SinkExt;
 | 
				
			||||||
    use std::convert::TryFrom;
 | 
					    use std::convert::TryFrom;
 | 
				
			||||||
    let server_op = match msg.map_err(|_e| ()).and_then(ServerOps::try_from) {
 | 
					    let server_op = match msg.map_err(|_e| ()).and_then(ServerOps::try_from) {
 | 
				
			||||||
@ -56,7 +59,7 @@ pub fn build_ldap_server<Backend>(
 | 
				
			|||||||
    server_builder: ServerBuilder,
 | 
					    server_builder: ServerBuilder,
 | 
				
			||||||
) -> Result<ServerBuilder>
 | 
					) -> Result<ServerBuilder>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    Backend: BackendHandler + 'static,
 | 
					    Backend: BackendHandler + LoginHandler + 'static,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use futures_util::StreamExt;
 | 
					    use futures_util::StreamExt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,8 +22,11 @@ mockall::mock! {
 | 
				
			|||||||
        fn clone(&self) -> Self;
 | 
					        fn clone(&self) -> Self;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    #[async_trait]
 | 
					    #[async_trait]
 | 
				
			||||||
    impl BackendHandler for TestTcpBackendHandler {
 | 
					    impl LoginHandler for TestTcpBackendHandler {
 | 
				
			||||||
        async fn bind(&self, request: BindRequest) -> DomainResult<()>;
 | 
					        async fn bind(&self, request: BindRequest) -> DomainResult<()>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #[async_trait]
 | 
				
			||||||
 | 
					    impl BackendHandler for TestTcpBackendHandler {
 | 
				
			||||||
        async fn list_users(&self, request: ListUsersRequest) -> DomainResult<Vec<User>>;
 | 
					        async fn list_users(&self, request: ListUsersRequest) -> DomainResult<Vec<User>>;
 | 
				
			||||||
        async fn list_groups(&self) -> DomainResult<Vec<Group>>;
 | 
					        async fn list_groups(&self) -> DomainResult<Vec<Group>>;
 | 
				
			||||||
        async fn get_user_groups(&self, user: String) -> DomainResult<HashSet<String>>;
 | 
					        async fn get_user_groups(&self, user: String) -> DomainResult<HashSet<String>>;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::handler::*,
 | 
					    domain::{
 | 
				
			||||||
 | 
					        handler::{BackendHandler, LoginHandler},
 | 
				
			||||||
 | 
					        opaque_handler::OpaqueHandler,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    infra::{auth_service, configuration::Configuration, tcp_api, tcp_backend_handler::*},
 | 
					    infra::{auth_service, configuration::Configuration, tcp_api, tcp_backend_handler::*},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use actix_files::{Files, NamedFile};
 | 
					use actix_files::{Files, NamedFile};
 | 
				
			||||||
@ -28,7 +31,9 @@ pub(crate) fn error_to_http_response(error: DomainError) -> HttpResponse {
 | 
				
			|||||||
        DomainError::AuthenticationError(_) | DomainError::AuthenticationProtocolError(_) => {
 | 
					        DomainError::AuthenticationError(_) | DomainError::AuthenticationProtocolError(_) => {
 | 
				
			||||||
            HttpResponse::Unauthorized()
 | 
					            HttpResponse::Unauthorized()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        DomainError::DatabaseError(_) => HttpResponse::InternalServerError(),
 | 
					        DomainError::DatabaseError(_) | DomainError::InternalError(_) => {
 | 
				
			||||||
 | 
					            HttpResponse::InternalServerError()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    .body(error.to_string())
 | 
					    .body(error.to_string())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -39,7 +44,7 @@ fn http_config<Backend>(
 | 
				
			|||||||
    jwt_secret: String,
 | 
					    jwt_secret: String,
 | 
				
			||||||
    jwt_blacklist: HashSet<u64>,
 | 
					    jwt_blacklist: HashSet<u64>,
 | 
				
			||||||
) where
 | 
					) where
 | 
				
			||||||
    Backend: TcpBackendHandler + BackendHandler + 'static,
 | 
					    Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + 'static,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    cfg.data(AppState::<Backend> {
 | 
					    cfg.data(AppState::<Backend> {
 | 
				
			||||||
        backend_handler,
 | 
					        backend_handler,
 | 
				
			||||||
@ -83,7 +88,7 @@ pub async fn build_tcp_server<Backend>(
 | 
				
			|||||||
    server_builder: ServerBuilder,
 | 
					    server_builder: ServerBuilder,
 | 
				
			||||||
) -> Result<ServerBuilder>
 | 
					) -> Result<ServerBuilder>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    Backend: TcpBackendHandler + BackendHandler + 'static,
 | 
					    Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + 'static,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    let jwt_secret = config.jwt_secret.clone();
 | 
					    let jwt_secret = config.jwt_secret.clone();
 | 
				
			||||||
    let jwt_blacklist = backend_handler.get_jwt_blacklist().await?;
 | 
					    let jwt_blacklist = backend_handler.get_jwt_blacklist().await?;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user