From 582a770e41ecd98aaf979417420e78a057e580d9 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Wed, 19 Mar 2025 02:51:58 +0100 Subject: [PATCH] Implement additionalGroups functionality (#2) --- queries/src/lib.rs | 37 +++++++++++++++ ...ueries__tests__create_user_gql_output.snap | 1 + ...queries__tests__get_groups_gql_output.snap | 10 ++++ .../queries__tests__get_user_gql_output.snap | 1 + ...ts__remove_user_from_group_gql_output.snap | 9 ++++ src/lldap.rs | 46 ++++++++++++++++++- src/resources.rs | 38 ++++++++++++++- 7 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 queries/src/snapshots/queries__tests__get_groups_gql_output.snap create mode 100644 queries/src/snapshots/queries__tests__remove_user_from_group_gql_output.snap diff --git a/queries/src/lib.rs b/queries/src/lib.rs index 23d1fdb..59d2987 100644 --- a/queries/src/lib.rs +++ b/queries/src/lib.rs @@ -43,6 +43,19 @@ pub struct AddUserToGroup { pub add_user_to_group: Success, } +#[derive(cynic::QueryVariables, Debug)] +pub struct RemoveUserFromGroupVariables<'a> { + pub group: i32, + pub username: &'a str, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Mutation", variables = "RemoveUserFromGroupVariables")] +pub struct RemoveUserFromGroup { + #[arguments(groupId: $group, userId: $username)] + pub remove_user_from_group: Success, +} + #[derive(cynic::QueryVariables, Debug)] pub struct GetUserVariables<'a> { pub username: &'a str, @@ -64,6 +77,13 @@ pub struct User { #[derive(cynic::QueryFragment, Debug)] pub struct Group { pub id: i32, + pub display_name: String, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query")] +pub struct GetGroups { + pub groups: Vec, } #[cfg(test)] @@ -96,10 +116,27 @@ mod tests { insta::assert_snapshot!(operation.query); } + #[test] + fn remove_user_from_group_gql_output() { + let operation = RemoveUserFromGroup::build(RemoveUserFromGroupVariables { + group: 3, + username: "user", + }); + + insta::assert_snapshot!(operation.query); + } + #[test] fn get_user_gql_output() { let operation = GetUser::build(GetUserVariables { username: "user" }); insta::assert_snapshot!(operation.query); } + + #[test] + fn get_groups_gql_output() { + let operation = GetGroups::build(()); + + insta::assert_snapshot!(operation.query); + } } diff --git a/queries/src/snapshots/queries__tests__create_user_gql_output.snap b/queries/src/snapshots/queries__tests__create_user_gql_output.snap index 01bc528..f320f31 100644 --- a/queries/src/snapshots/queries__tests__create_user_gql_output.snap +++ b/queries/src/snapshots/queries__tests__create_user_gql_output.snap @@ -7,6 +7,7 @@ mutation CreateUser($username: String!) { id groups { id + displayName } } } diff --git a/queries/src/snapshots/queries__tests__get_groups_gql_output.snap b/queries/src/snapshots/queries__tests__get_groups_gql_output.snap new file mode 100644 index 0000000..a8cb93b --- /dev/null +++ b/queries/src/snapshots/queries__tests__get_groups_gql_output.snap @@ -0,0 +1,10 @@ +--- +source: queries/src/lib.rs +expression: operation.query +--- +query GetGroups { + groups { + id + displayName + } +} diff --git a/queries/src/snapshots/queries__tests__get_user_gql_output.snap b/queries/src/snapshots/queries__tests__get_user_gql_output.snap index 7702fd0..dcd869a 100644 --- a/queries/src/snapshots/queries__tests__get_user_gql_output.snap +++ b/queries/src/snapshots/queries__tests__get_user_gql_output.snap @@ -7,6 +7,7 @@ query GetUser($username: String!) { id groups { id + displayName } } } diff --git a/queries/src/snapshots/queries__tests__remove_user_from_group_gql_output.snap b/queries/src/snapshots/queries__tests__remove_user_from_group_gql_output.snap new file mode 100644 index 0000000..55fbf18 --- /dev/null +++ b/queries/src/snapshots/queries__tests__remove_user_from_group_gql_output.snap @@ -0,0 +1,9 @@ +--- +source: queries/src/lib.rs +expression: operation.query +--- +mutation RemoveUserFromGroup($group: Int!, $username: String!) { + removeUserFromGroup(groupId: $group, userId: $username) { + ok + } +} diff --git a/src/lldap.rs b/src/lldap.rs index 414dbe2..73fc66c 100644 --- a/src/lldap.rs +++ b/src/lldap.rs @@ -10,8 +10,9 @@ use cynic::http::{CynicReqwestError, ReqwestExt}; use cynic::{GraphQlError, GraphQlResponse, MutationBuilder, QueryBuilder}; use lldap_auth::login::{ClientSimpleLoginRequest, ServerLoginResponse}; use queries::{ - CreateUser, CreateUserVariables, DeleteUser, DeleteUserVariables, GetUser, GetUserVariables, - User, + AddUserToGroup, AddUserToGroupVariables, CreateUser, CreateUserVariables, DeleteUser, + DeleteUserVariables, GetGroups, GetUser, GetUserVariables, Group, RemoveUserFromGroup, + RemoveUserFromGroupVariables, User, }; #[derive(thiserror::Error, Debug)] @@ -137,6 +138,47 @@ impl LldapClient { Ok(()) } + pub async fn get_groups(&self) -> Result> { + let operation = GetGroups::build(()); + + let response = self + .client + .post(format!("{}/api/graphql", self.url)) + .run_graphql(operation) + .await?; + + Ok(check_graphql_errors(response)?.groups) + } + + pub async fn add_user_to_group(&self, username: &str, group: i32) -> Result<()> { + let operation = AddUserToGroup::build(AddUserToGroupVariables { username, group }); + + let response = self + .client + .post(format!("{}/api/graphql", self.url)) + .run_graphql(operation) + .await?; + + check_graphql_errors(response)?; + + Ok(()) + } + + pub async fn remove_user_from_group(&self, username: &str, group: i32) -> Result<()> { + let operation = + RemoveUserFromGroup::build(RemoveUserFromGroupVariables { username, group }); + + let response = self + .client + .post(format!("{}/api/graphql", self.url)) + .run_graphql(operation) + .await?; + + check_graphql_errors(response)?; + + Ok(()) + } + pub async fn update_password(&self, username: &str, password: &str) -> Result<()> { let mut rng = rand::rngs::OsRng; let registration_start_request = diff --git a/src/resources.rs b/src/resources.rs index 904b9e9..bbf3a8e 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -199,7 +199,7 @@ impl Reconcile for ServiceUser { let lldap_client = ctx.lldap_config.build_client().await?; trace!(name, "Creating user if needed"); - let _user = match lldap_client.get_user(&username).await { + let user = match lldap_client.get_user(&username).await { Err(lldap::Error::GraphQl(err)) if err.message == format!("Entity not found: `{username}`") => { @@ -218,6 +218,42 @@ impl Reconcile for ServiceUser { Err(err) => Err(err), }?; + let groups = lldap_client.get_groups().await?; + // TODO: Error when invalid name + let needed_groups: Vec<_> = self + .spec + .additional_groups + .iter() + .filter_map(|additional_group| { + groups + .iter() + .find(|group| &group.display_name == additional_group) + .map(|group| group.id) + }) + .collect(); + + let current_groups: Vec<_> = user.groups.iter().map(|group| group.id).collect(); + + let remove = current_groups + .iter() + .filter(|group| !needed_groups.contains(group)); + for &group in remove { + trace!(name, username, group, "Removing user from group"); + + lldap_client + .remove_user_from_group(&username, group) + .await?; + } + + let add = needed_groups + .iter() + .filter(|group| !current_groups.contains(group)); + for &group in add { + trace!(name, username, group, "Adding user to group"); + + lldap_client.add_user_to_group(&username, group).await?; + } + trace!(name, "Updating password"); let password = secret.get().data.as_ref().unwrap().get("password").unwrap(); let password = from_utf8(&password.0).unwrap();