server: statically enforce access control

This commit is contained in:
Valentin Tolmer
2023-02-17 15:59:32 +01:00
committed by nitnelave
parent 322bf26db5
commit c9997d4c17
18 changed files with 712 additions and 359 deletions

View File

@@ -122,13 +122,17 @@ pub struct UpdateGroupRequest {
}
#[async_trait]
pub trait LoginHandler: Clone + Send {
pub trait LoginHandler: Send + Sync {
async fn bind(&self, request: BindRequest) -> Result<()>;
}
#[async_trait]
pub trait GroupBackendHandler {
pub trait GroupListerBackendHandler {
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
}
#[async_trait]
pub trait GroupBackendHandler {
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
@@ -136,12 +140,16 @@ pub trait GroupBackendHandler {
}
#[async_trait]
pub trait UserBackendHandler {
pub trait UserListerBackendHandler {
async fn list_users(
&self,
filters: Option<UserRequestFilter>,
get_groups: bool,
) -> Result<Vec<UserAndGroups>>;
}
#[async_trait]
pub trait UserBackendHandler {
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
@@ -152,7 +160,15 @@ pub trait UserBackendHandler {
}
#[async_trait]
pub trait BackendHandler: Clone + Send + GroupBackendHandler + UserBackendHandler {}
pub trait BackendHandler:
Send
+ Sync
+ GroupBackendHandler
+ UserBackendHandler
+ UserListerBackendHandler
+ GroupListerBackendHandler
{
}
#[cfg(test)]
mockall::mock! {
@@ -161,16 +177,22 @@ mockall::mock! {
fn clone(&self) -> Self;
}
#[async_trait]
impl GroupBackendHandler for TestBackendHandler {
impl GroupListerBackendHandler for TestBackendHandler {
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
}
#[async_trait]
impl GroupBackendHandler for TestBackendHandler {
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
}
#[async_trait]
impl UserBackendHandler for TestBackendHandler {
impl UserListerBackendHandler for TestBackendHandler {
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
}
#[async_trait]
impl UserBackendHandler for TestBackendHandler {
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;

View File

@@ -1,10 +1,10 @@
use ldap3_proto::{
proto::LdapOp, LdapFilter, LdapPartialAttribute, LdapResultCode, LdapSearchResultEntry,
};
use tracing::{debug, info, instrument, warn};
use tracing::{debug, instrument, warn};
use crate::domain::{
handler::{BackendHandler, GroupRequestFilter},
handler::{GroupListerBackendHandler, GroupRequestFilter},
ldap::error::LdapError,
types::{Group, GroupColumn, UserId, Uuid},
};
@@ -21,7 +21,7 @@ pub fn get_group_attribute(
group: &Group,
base_dn_str: &str,
attribute: &str,
user_filter: &Option<&UserId>,
user_filter: &Option<UserId>,
ignored_group_attributes: &[String],
) -> Option<Vec<Vec<u8>>> {
let attribute = attribute.to_ascii_lowercase();
@@ -34,7 +34,7 @@ pub fn get_group_attribute(
"member" | "uniquemember" => group
.users
.iter()
.filter(|u| user_filter.map(|f| *u == f).unwrap_or(true))
.filter(|u| user_filter.as_ref().map(|f| *u == f).unwrap_or(true))
.map(|u| format!("uid={},ou=people,{}", u, base_dn_str).into_bytes())
.collect(),
"1.1" => return None,
@@ -81,7 +81,7 @@ fn make_ldap_search_group_result_entry(
group: Group,
base_dn_str: &str,
attributes: &[String],
user_filter: &Option<&UserId>,
user_filter: &Option<UserId>,
ignored_group_attributes: &[String],
) -> LdapSearchResultEntry {
let expanded_attributes = expand_group_attribute_wildcards(attributes);
@@ -201,25 +201,17 @@ fn convert_group_filter(
}
#[instrument(skip_all, level = "debug")]
pub async fn get_groups_list<Backend: BackendHandler>(
pub async fn get_groups_list<Backend: GroupListerBackendHandler>(
ldap_info: &LdapInfo,
ldap_filter: &LdapFilter,
base: &str,
user_filter: &Option<&UserId>,
backend: &mut Backend,
backend: &Backend,
) -> LdapResult<Vec<Group>> {
debug!(?ldap_filter);
let filter = convert_group_filter(ldap_info, ldap_filter)?;
let parsed_filters = match user_filter {
None => filter,
Some(u) => {
info!("Unprivileged search, limiting results");
GroupRequestFilter::And(vec![filter, GroupRequestFilter::Member((*u).clone())])
}
};
debug!(?parsed_filters);
let filters = convert_group_filter(ldap_info, ldap_filter)?;
debug!(?filters);
backend
.list_groups(Some(parsed_filters))
.list_groups(Some(filters))
.await
.map_err(|e| LdapError {
code: LdapResultCode::Other,
@@ -231,7 +223,7 @@ pub fn convert_groups_to_ldap_op<'a>(
groups: Vec<Group>,
attributes: &'a [String],
ldap_info: &'a LdapInfo,
user_filter: &'a Option<&'a UserId>,
user_filter: &'a Option<UserId>,
) -> impl Iterator<Item = LdapOp> + 'a {
groups.into_iter().map(move |g| {
LdapOp::SearchResultEntry(make_ldap_search_group_result_entry(

View File

@@ -2,10 +2,10 @@ use chrono::TimeZone;
use ldap3_proto::{
proto::LdapOp, LdapFilter, LdapPartialAttribute, LdapResultCode, LdapSearchResultEntry,
};
use tracing::{debug, info, instrument, warn};
use tracing::{debug, instrument, warn};
use crate::domain::{
handler::{BackendHandler, UserRequestFilter},
handler::{UserListerBackendHandler, UserRequestFilter},
ldap::{
error::LdapError,
utils::{expand_attribute_wildcards, get_user_id_from_distinguished_name},
@@ -217,26 +217,18 @@ fn expand_user_attribute_wildcards(attributes: &[String]) -> Vec<&str> {
}
#[instrument(skip_all, level = "debug")]
pub async fn get_user_list<Backend: BackendHandler>(
pub async fn get_user_list<Backend: UserListerBackendHandler>(
ldap_info: &LdapInfo,
ldap_filter: &LdapFilter,
request_groups: bool,
base: &str,
user_filter: &Option<&UserId>,
backend: &mut Backend,
backend: &Backend,
) -> LdapResult<Vec<UserAndGroups>> {
debug!(?ldap_filter);
let filters = convert_user_filter(ldap_info, ldap_filter)?;
let parsed_filters = match user_filter {
None => filters,
Some(u) => {
info!("Unprivileged search, limiting results");
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
}
};
debug!(?parsed_filters);
debug!(?filters);
backend
.list_users(Some(parsed_filters), request_groups)
.list_users(Some(filters), request_groups)
.await
.map_err(|e| LdapError {
code: LdapResultCode::Other,

View File

@@ -4,7 +4,7 @@ use async_trait::async_trait;
pub use lldap_auth::{login, registration};
#[async_trait]
pub trait OpaqueHandler: Clone + Send {
pub trait OpaqueHandler: Send + Sync {
async fn login_start(
&self,
request: login::ClientLoginStartRequest,

View File

@@ -1,4 +1,4 @@
use super::{handler::BackendHandler, sql_tables::DbConnection};
use crate::domain::{handler::BackendHandler, sql_tables::DbConnection};
use crate::infra::configuration::Configuration;
use async_trait::async_trait;
@@ -23,7 +23,8 @@ pub mod tests {
use crate::{
domain::{
handler::{
CreateUserRequest, GroupBackendHandler, UserBackendHandler, UserRequestFilter,
CreateUserRequest, GroupBackendHandler, UserBackendHandler,
UserListerBackendHandler, UserRequestFilter,
},
sql_tables::init_table,
types::{GroupId, UserId},

View File

@@ -1,6 +1,8 @@
use crate::domain::{
error::{DomainError, Result},
handler::{GroupBackendHandler, GroupRequestFilter, UpdateGroupRequest},
handler::{
GroupBackendHandler, GroupListerBackendHandler, GroupRequestFilter, UpdateGroupRequest,
},
model::{self, GroupColumn, MembershipColumn},
sql_backend_handler::SqlBackendHandler,
types::{Group, GroupDetails, GroupId, Uuid},
@@ -57,7 +59,7 @@ fn get_group_filter_expr(filter: GroupRequestFilter) -> Cond {
}
#[async_trait]
impl GroupBackendHandler for SqlBackendHandler {
impl GroupListerBackendHandler for SqlBackendHandler {
#[instrument(skip_all, level = "debug", ret, err)]
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> {
debug!(?filters);
@@ -94,7 +96,10 @@ impl GroupBackendHandler for SqlBackendHandler {
})
.collect())
}
}
#[async_trait]
impl GroupBackendHandler for SqlBackendHandler {
#[instrument(skip_all, level = "debug", ret, err)]
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails> {
debug!(?group_id);

View File

@@ -1,6 +1,9 @@
use super::{
use crate::domain::{
error::{DomainError, Result},
handler::{CreateUserRequest, UpdateUserRequest, UserBackendHandler, UserRequestFilter},
handler::{
CreateUserRequest, UpdateUserRequest, UserBackendHandler, UserListerBackendHandler,
UserRequestFilter,
},
model::{self, GroupColumn, UserColumn},
sql_backend_handler::SqlBackendHandler,
types::{GroupDetails, GroupId, User, UserAndGroups, UserId, Uuid},
@@ -70,7 +73,7 @@ fn to_value(opt_name: &Option<String>) -> ActiveValue<Option<String>> {
}
#[async_trait]
impl UserBackendHandler for SqlBackendHandler {
impl UserListerBackendHandler for SqlBackendHandler {
#[instrument(skip_all, level = "debug", ret, err)]
async fn list_users(
&self,
@@ -135,7 +138,10 @@ impl UserBackendHandler for SqlBackendHandler {
.collect())
}
}
}
#[async_trait]
impl UserBackendHandler for SqlBackendHandler {
#[instrument(skip_all, level = "debug", ret)]
async fn get_user_details(&self, user_id: &UserId) -> Result<User> {
debug!(?user_id);