server: Introduce a read-only user

This commit is contained in:
Valentin Tolmer 2022-06-06 16:48:22 +02:00 committed by nitnelave
parent 1efab58d0c
commit ff698df280
5 changed files with 161 additions and 82 deletions

View File

@ -438,7 +438,11 @@ where
} }
.into_inner(); .into_inner();
let user_id = &registration_start_request.username; let user_id = &registration_start_request.username;
validation_result.can_access(user_id); if !validation_result.can_write(user_id) {
return ApiResult::Right(
HttpResponse::Unauthorized().body("Not authorized to change the user's password"),
);
}
data.backend_handler data.backend_handler
.registration_start(registration_start_request) .registration_start(registration_start_request)
.await .await
@ -519,9 +523,16 @@ where
} }
} }
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Permission {
Admin,
Readonly,
Regular,
}
pub struct ValidationResults { pub struct ValidationResults {
pub user: String, pub user: String,
pub is_admin: bool, pub permission: Permission,
} }
impl ValidationResults { impl ValidationResults {
@ -529,12 +540,30 @@ impl ValidationResults {
pub fn admin() -> Self { pub fn admin() -> Self {
Self { Self {
user: "admin".to_string(), user: "admin".to_string(),
is_admin: true, permission: Permission::Admin,
} }
} }
pub fn can_access(&self, user: &str) -> bool { #[must_use]
self.is_admin || self.user == user pub fn is_admin(&self) -> bool {
self.permission == Permission::Admin
}
#[must_use]
pub fn is_admin_or_readonly(&self) -> bool {
self.permission == Permission::Admin || self.permission == Permission::Readonly
}
#[must_use]
pub fn can_read(&self, user: &str) -> bool {
self.permission == Permission::Admin
|| self.permission == Permission::Readonly
|| self.user == user
}
#[must_use]
pub fn can_write(&self, user: &str) -> bool {
self.permission == Permission::Admin || self.user == user
} }
} }
@ -561,10 +590,16 @@ pub(crate) fn check_if_token_is_valid<Backend>(
if state.jwt_blacklist.read().unwrap().contains(&jwt_hash) { if state.jwt_blacklist.read().unwrap().contains(&jwt_hash) {
return Err(ErrorUnauthorized("JWT was logged out")); return Err(ErrorUnauthorized("JWT was logged out"));
} }
let is_admin = token.claims().groups.contains("lldap_admin"); let is_in_group = |name| token.claims().groups.contains(name);
Ok(ValidationResults { Ok(ValidationResults {
user: token.claims().user.clone(), user: token.claims().user.clone(),
is_admin, permission: if is_in_group("lldap_admin") {
Permission::Admin
} else if is_in_group("lldap_readonly") {
Permission::Readonly
} else {
Permission::Regular
},
}) })
} }

View File

@ -63,7 +63,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
context: &Context<Handler>, context: &Context<Handler>,
user: CreateUserInput, user: CreateUserInput,
) -> FieldResult<super::query::User<Handler>> { ) -> FieldResult<super::query::User<Handler>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized user creation".into()); return Err("Unauthorized user creation".into());
} }
let user_id = UserId::new(&user.id); let user_id = UserId::new(&user.id);
@ -88,7 +88,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
context: &Context<Handler>, context: &Context<Handler>,
name: String, name: String,
) -> FieldResult<super::query::Group<Handler>> { ) -> FieldResult<super::query::Group<Handler>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized group creation".into()); return Err("Unauthorized group creation".into());
} }
let group_id = context.handler.create_group(&name).await?; let group_id = context.handler.create_group(&name).await?;
@ -103,7 +103,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
context: &Context<Handler>, context: &Context<Handler>,
user: UpdateUserInput, user: UpdateUserInput,
) -> FieldResult<Success> { ) -> FieldResult<Success> {
if !context.validation_result.can_access(&user.id) { if !context.validation_result.can_write(&user.id) {
return Err("Unauthorized user update".into()); return Err("Unauthorized user update".into());
} }
context context
@ -123,7 +123,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
context: &Context<Handler>, context: &Context<Handler>,
group: UpdateGroupInput, group: UpdateGroupInput,
) -> FieldResult<Success> { ) -> FieldResult<Success> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized group update".into()); return Err("Unauthorized group update".into());
} }
if group.id == 1 { if group.id == 1 {
@ -144,7 +144,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
user_id: String, user_id: String,
group_id: i32, group_id: i32,
) -> FieldResult<Success> { ) -> FieldResult<Success> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized group membership modification".into()); return Err("Unauthorized group membership modification".into());
} }
context context
@ -159,7 +159,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
user_id: String, user_id: String,
group_id: i32, group_id: i32,
) -> FieldResult<Success> { ) -> FieldResult<Success> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized group membership modification".into()); return Err("Unauthorized group membership modification".into());
} }
if context.validation_result.user == user_id && group_id == 1 { if context.validation_result.user == user_id && group_id == 1 {
@ -173,7 +173,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
} }
async fn delete_user(context: &Context<Handler>, user_id: String) -> FieldResult<Success> { async fn delete_user(context: &Context<Handler>, user_id: String) -> FieldResult<Success> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized user deletion".into()); return Err("Unauthorized user deletion".into());
} }
if context.validation_result.user == user_id { if context.validation_result.user == user_id {
@ -184,7 +184,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
} }
async fn delete_group(context: &Context<Handler>, group_id: i32) -> FieldResult<Success> { async fn delete_group(context: &Context<Handler>, group_id: i32) -> FieldResult<Success> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin() {
return Err("Unauthorized group deletion".into()); return Err("Unauthorized group deletion".into());
} }
if group_id == 1 { if group_id == 1 {

View File

@ -107,7 +107,7 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
} }
pub async fn user(context: &Context<Handler>, user_id: String) -> FieldResult<User<Handler>> { pub async fn user(context: &Context<Handler>, user_id: String) -> FieldResult<User<Handler>> {
if !context.validation_result.can_access(&user_id) { if !context.validation_result.can_read(&user_id) {
return Err("Unauthorized access to user data".into()); return Err("Unauthorized access to user data".into());
} }
Ok(context Ok(context
@ -121,7 +121,7 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
context: &Context<Handler>, context: &Context<Handler>,
#[graphql(name = "where")] filters: Option<RequestFilter>, #[graphql(name = "where")] filters: Option<RequestFilter>,
) -> FieldResult<Vec<User<Handler>>> { ) -> FieldResult<Vec<User<Handler>>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin_or_readonly() {
return Err("Unauthorized access to user list".into()); return Err("Unauthorized access to user list".into());
} }
Ok(context Ok(context
@ -132,7 +132,7 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
} }
async fn groups(context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> { async fn groups(context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin_or_readonly() {
return Err("Unauthorized access to group list".into()); return Err("Unauthorized access to group list".into());
} }
Ok(context Ok(context
@ -143,7 +143,7 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
} }
async fn group(context: &Context<Handler>, group_id: i32) -> FieldResult<Group<Handler>> { async fn group(context: &Context<Handler>, group_id: i32) -> FieldResult<Group<Handler>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin_or_readonly() {
return Err("Unauthorized access to group data".into()); return Err("Unauthorized access to group data".into());
} }
Ok(context Ok(context
@ -234,7 +234,7 @@ impl<Handler: BackendHandler + Sync> Group<Handler> {
} }
/// The groups to which this user belongs. /// The groups to which this user belongs.
async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> { async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin_or_readonly() {
return Err("Unauthorized access to group data".into()); return Err("Unauthorized access to group data".into());
} }
Ok(context Ok(context

View File

@ -1,9 +1,12 @@
use crate::domain::{ use crate::{
domain::{
handler::{ handler::{
BackendHandler, BindRequest, Group, GroupRequestFilter, LoginHandler, User, UserId, BackendHandler, BindRequest, Group, GroupRequestFilter, LoginHandler, User, UserId,
UserRequestFilter, UserRequestFilter,
}, },
opaque_handler::OpaqueHandler, opaque_handler::OpaqueHandler,
},
infra::auth_service::Permission,
}; };
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use itertools::Itertools; use itertools::Itertools;
@ -378,12 +381,6 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
}) })
} }
#[derive(Clone, Copy, PartialEq, Debug)]
enum Permission {
Admin,
Regular,
}
pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> { pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
user_info: Option<(UserId, Permission)>, user_info: Option<(UserId, Permission)>,
backend_handler: Backend, backend_handler: Backend,
@ -436,16 +433,19 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
.await .await
{ {
Ok(()) => { Ok(()) => {
let is_admin = self let user_groups = self.backend_handler.get_user_groups(&user_id).await;
.backend_handler let is_in_group = |name| {
.get_user_groups(&user_id) user_groups
.await .as_ref()
.map(|groups| groups.iter().any(|g| g.1 == "lldap_admin")) .map(|groups| groups.iter().any(|g| g.1 == name))
.unwrap_or(false); .unwrap_or(false)
};
self.user_info = Some(( self.user_info = Some((
user_id, user_id,
if is_admin { if is_in_group("lldap_admin") {
Permission::Admin Permission::Admin
} else if is_in_group("lldap_readonly") {
Permission::Readonly
} else { } else {
Permission::Regular Permission::Regular
}, },
@ -497,10 +497,10 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
match get_user_id_from_distinguished_name(user, &self.base_dn, &self.base_dn_str) { match get_user_id_from_distinguished_name(user, &self.base_dn, &self.base_dn_str) {
Ok(uid) => { Ok(uid) => {
if *permission != Permission::Admin && user_id != &uid { if *permission != Permission::Admin && user_id != &uid {
return vec![make_search_error( return vec![make_extended_response(
LdapResultCode::InsufficentAccessRights, LdapResultCode::InsufficentAccessRights,
format!( format!(
r#"User {} cannot modify the password of user {}"#, r#"User `{}` cannot modify the password of user `{}`"#,
&user_id, &uid &user_id, &uid
), ),
)]; )];
@ -542,7 +542,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> { pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> {
let user_filter = match &self.user_info { let user_filter = match &self.user_info {
Some((_, Permission::Admin)) => None, Some((_, Permission::Admin)) | Some((_, Permission::Readonly)) => None,
Some((user_id, Permission::Regular)) => Some(user_id), Some((user_id, Permission::Regular)) => Some(user_id),
None => { None => {
return vec![make_search_error( return vec![make_search_error(
@ -959,8 +959,9 @@ mod tests {
make_search_request::<S>("ou=people,Dc=example,dc=com", filter, attrs) make_search_request::<S>("ou=people,Dc=example,dc=com", filter, attrs)
} }
async fn setup_bound_handler( async fn setup_bound_handler_with_group(
mut mock: MockTestBackendHandler, mut mock: MockTestBackendHandler,
group: &str,
) -> LdapHandler<MockTestBackendHandler> { ) -> LdapHandler<MockTestBackendHandler> {
mock.expect_bind() mock.expect_bind()
.with(eq(BindRequest { .with(eq(BindRequest {
@ -968,11 +969,12 @@ mod tests {
password: "pass".to_string(), password: "pass".to_string(),
})) }))
.return_once(|_| Ok(())); .return_once(|_| Ok(()));
let group = group.to_string();
mock.expect_get_user_groups() mock.expect_get_user_groups()
.with(eq(UserId::new("test"))) .with(eq(UserId::new("test")))
.return_once(|_| { .return_once(|_| {
let mut set = HashSet::new(); let mut set = HashSet::new();
set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string())); set.insert(GroupIdAndName(GroupId(42), group));
Ok(set) Ok(set)
}); });
let mut ldap_handler = let mut ldap_handler =
@ -988,6 +990,18 @@ mod tests {
ldap_handler ldap_handler
} }
async fn setup_bound_readonly_handler(
mock: MockTestBackendHandler,
) -> LdapHandler<MockTestBackendHandler> {
setup_bound_handler_with_group(mock, "lldap_readonly").await
}
async fn setup_bound_admin_handler(
mock: MockTestBackendHandler,
) -> LdapHandler<MockTestBackendHandler> {
setup_bound_handler_with_group(mock, "lldap_admin").await
}
#[tokio::test] #[tokio::test]
async fn test_bind() { async fn test_bind() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
@ -1053,15 +1067,8 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn test_search_non_admin_user() { async fn test_search_regular_user() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_bind()
.with(eq(crate::domain::handler::BindRequest {
name: UserId::new("test"),
password: "pass".to_string(),
}))
.times(1)
.return_once(|_| Ok(()));
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![ .with(eq(Some(UserRequestFilter::And(vec![
UserRequestFilter::And(vec![]), UserRequestFilter::And(vec![]),
@ -1074,20 +1081,7 @@ mod tests {
..Default::default() ..Default::default()
}]) }])
}); });
mock.expect_get_user_groups() let mut ldap_handler = setup_bound_handler_with_group(mock, "regular").await;
.with(eq(UserId::new("test")))
.return_once(|_| Ok(HashSet::new()));
let mut ldap_handler =
LdapHandler::new(mock, "dc=example,dc=com".to_string(), vec![], vec![]);
let request = LdapBindRequest {
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
cred: LdapBindCred::Simple("pass".to_string()),
};
assert_eq!(
ldap_handler.do_bind(&request).await.0,
LdapResultCode::Success
);
let request = let request =
make_user_search_request::<String>(LdapFilter::And(vec![]), vec!["1.1".to_string()]); make_user_search_request::<String>(LdapFilter::And(vec![]), vec!["1.1".to_string()]);
@ -1103,6 +1097,23 @@ mod tests {
); );
} }
#[tokio::test]
async fn test_search_readonly_user() {
let mut mock = MockTestBackendHandler::new();
mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![]))))
.times(1)
.return_once(|_| Ok(vec![]));
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
let request =
make_user_search_request::<String>(LdapFilter::And(vec![]), vec!["1.1".to_string()]);
assert_eq!(
ldap_handler.do_search(&request).await,
vec![make_search_success()],
);
}
#[tokio::test] #[tokio::test]
async fn test_bind_invalid_dn() { async fn test_bind_invalid_dn() {
let mock = MockTestBackendHandler::new(); let mock = MockTestBackendHandler::new();
@ -1208,7 +1219,7 @@ mod tests {
}, },
]) ])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::And(vec![]), LdapFilter::And(vec![]),
vec![ vec![
@ -1334,7 +1345,7 @@ mod tests {
}, },
]) ])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=cOm", "ou=groups,dc=example,dc=cOm",
LdapFilter::And(vec![]), LdapFilter::And(vec![]),
@ -1417,7 +1428,7 @@ mod tests {
users: vec![], users: vec![],
}]) }])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=com", "ou=groups,dc=example,dc=com",
LdapFilter::And(vec![ LdapFilter::And(vec![
@ -1466,7 +1477,7 @@ mod tests {
users: vec![], users: vec![],
}]) }])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=com", "ou=groups,dc=example,dc=com",
LdapFilter::Or(vec![LdapFilter::Not(Box::new(LdapFilter::Equality( LdapFilter::Or(vec![LdapFilter::Not(Box::new(LdapFilter::Equality(
@ -1505,7 +1516,7 @@ mod tests {
"Error getting groups".to_string(), "Error getting groups".to_string(),
)) ))
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=com", "ou=groups,dc=example,dc=com",
LdapFilter::Or(vec![LdapFilter::Not(Box::new(LdapFilter::Equality( LdapFilter::Or(vec![LdapFilter::Not(Box::new(LdapFilter::Equality(
@ -1525,7 +1536,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_search_groups_filter_error() { async fn test_search_groups_filter_error() {
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await; let mut ldap_handler = setup_bound_admin_handler(MockTestBackendHandler::new()).await;
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=com", "ou=groups,dc=example,dc=com",
LdapFilter::And(vec![LdapFilter::Substring( LdapFilter::And(vec![LdapFilter::Substring(
@ -1561,7 +1572,7 @@ mod tests {
])))) ]))))
.times(1) .times(1)
.return_once(|_| Ok(vec![])); .return_once(|_| Ok(vec![]));
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::And(vec![LdapFilter::Or(vec![ LdapFilter::And(vec![LdapFilter::Or(vec![
LdapFilter::Not(Box::new(LdapFilter::Equality( LdapFilter::Not(Box::new(LdapFilter::Equality(
@ -1590,7 +1601,7 @@ mod tests {
.with(eq(Some(UserRequestFilter::MemberOf("group_1".to_string())))) .with(eq(Some(UserRequestFilter::MemberOf("group_1".to_string()))))
.times(1) .times(1)
.return_once(|_| Ok(vec![])); .return_once(|_| Ok(vec![]));
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::Equality( LdapFilter::Equality(
"memberOf".to_string(), "memberOf".to_string(),
@ -1645,7 +1656,7 @@ mod tests {
..Default::default() ..Default::default()
}]) }])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::And(vec![LdapFilter::Or(vec![LdapFilter::Not(Box::new( LdapFilter::And(vec![LdapFilter::Or(vec![LdapFilter::Not(Box::new(
LdapFilter::Equality("givenname".to_string(), "bob".to_string()), LdapFilter::Equality("givenname".to_string(), "bob".to_string()),
@ -1695,7 +1706,7 @@ mod tests {
users: vec![UserId::new("bob"), UserId::new("john")], users: vec![UserId::new("bob"), UserId::new("john")],
}]) }])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_search_request( let request = make_search_request(
"dc=example,dc=com", "dc=example,dc=com",
LdapFilter::And(vec![]), LdapFilter::And(vec![]),
@ -1771,7 +1782,7 @@ mod tests {
users: vec![UserId::new("bob"), UserId::new("john")], users: vec![UserId::new("bob"), UserId::new("john")],
}]) }])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
// Test simple wildcard // Test simple wildcard
let request = let request =
@ -1895,7 +1906,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_search_wrong_base() { async fn test_search_wrong_base() {
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await; let mut ldap_handler = setup_bound_admin_handler(MockTestBackendHandler::new()).await;
let request = make_search_request( let request = make_search_request(
"ou=users,dc=example,dc=com", "ou=users,dc=example,dc=com",
LdapFilter::And(vec![]), LdapFilter::And(vec![]),
@ -1909,7 +1920,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_search_unsupported_filters() { async fn test_search_unsupported_filters() {
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await; let mut ldap_handler = setup_bound_admin_handler(MockTestBackendHandler::new()).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::Substring( LdapFilter::Substring(
"uid".to_string(), "uid".to_string(),
@ -1952,7 +1963,7 @@ mod tests {
mock.expect_registration_finish() mock.expect_registration_finish()
.times(1) .times(1)
.return_once(|_| Ok(())); .return_once(|_| Ok(()));
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = LdapOp::ExtendedRequest( let request = LdapOp::ExtendedRequest(
LdapPasswordModifyRequest { LdapPasswordModifyRequest {
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()), user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
@ -1972,7 +1983,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_password_change_errors() { async fn test_password_change_errors() {
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await; let mut ldap_handler = setup_bound_admin_handler(MockTestBackendHandler::new()).await;
let request = LdapOp::ExtendedRequest( let request = LdapOp::ExtendedRequest(
LdapPasswordModifyRequest { LdapPasswordModifyRequest {
user_identity: None, user_identity: None,
@ -2016,9 +2027,29 @@ mod tests {
); );
} }
#[tokio::test]
async fn test_password_change_unauthorized() {
let mut ldap_handler = setup_bound_readonly_handler(MockTestBackendHandler::new()).await;
let request = LdapOp::ExtendedRequest(
LdapPasswordModifyRequest {
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
old_password: Some("pass".to_string()),
new_password: Some("password".to_string()),
}
.into(),
);
assert_eq!(
ldap_handler.handle_ldap_message(request).await,
Some(vec![make_extended_response(
LdapResultCode::InsufficentAccessRights,
"User `test` cannot modify the password of user `bob`".to_string(),
)])
);
}
#[tokio::test] #[tokio::test]
async fn test_search_root_dse() { async fn test_search_root_dse() {
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await; let mut ldap_handler = setup_bound_admin_handler(MockTestBackendHandler::new()).await;
let request = LdapSearchRequest { let request = LdapSearchRequest {
base: "".to_string(), base: "".to_string(),
scope: LdapSearchScope::Base, scope: LdapSearchScope::Base,

View File

@ -4,7 +4,7 @@
use crate::{ use crate::{
domain::{ domain::{
handler::{BackendHandler, CreateUserRequest}, handler::{BackendHandler, CreateUserRequest, GroupRequestFilter},
sql_backend_handler::SqlBackendHandler, sql_backend_handler::SqlBackendHandler,
sql_opaque_handler::register_password, sql_opaque_handler::register_password,
sql_tables::PoolOptions, sql_tables::PoolOptions,
@ -62,6 +62,19 @@ async fn run_server(config: Configuration) -> Result<()> {
.map_err(|e| anyhow!("Error setting up admin login/account: {:#}", e)) .map_err(|e| anyhow!("Error setting up admin login/account: {:#}", e))
.context("while creating the admin user")?; .context("while creating the admin user")?;
} }
if backend_handler
.list_groups(Some(GroupRequestFilter::DisplayName(
"lldap_readonly".to_string(),
)))
.await?
.is_empty()
{
warn!("Could not find readonly group, trying to create it");
backend_handler
.create_group("lldap_readonly")
.await
.context("while creating readonly group")?;
}
let server_builder = infra::ldap_server::build_ldap_server( let server_builder = infra::ldap_server::build_ldap_server(
&config, &config,
backend_handler.clone(), backend_handler.clone(),