From 685746796ea2c8a1c804cfad33cb6eb14e979aa4 Mon Sep 17 00:00:00 2001
From: Doridian <git@doridian.net>
Date: Sun, 29 Dec 2024 21:29:28 -0800
Subject: [PATCH 1/4] Add and implement basic NssGroupsByMember call

---
 unix_integration/common/src/unix_proto.rs      |  2 ++
 .../resolver/src/bin/kanidm_unixd.rs           |  8 ++++++++
 unix_integration/resolver/src/resolver.rs      | 18 ++++++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/unix_integration/common/src/unix_proto.rs b/unix_integration/common/src/unix_proto.rs
index 69f88c100..5d1afb06c 100644
--- a/unix_integration/common/src/unix_proto.rs
+++ b/unix_integration/common/src/unix_proto.rs
@@ -121,6 +121,7 @@ pub enum ClientRequest {
     NssGroups,
     NssGroupByGid(u32),
     NssGroupByName(String),
+    NssGroupsByMember(String),
     PamAuthenticateInit {
         account_id: String,
         info: PamServiceInfo,
@@ -144,6 +145,7 @@ impl ClientRequest {
             ClientRequest::NssGroups => "NssGroups".to_string(),
             ClientRequest::NssGroupByGid(id) => format!("NssGroupByGid({})", id),
             ClientRequest::NssGroupByName(id) => format!("NssGroupByName({})", id),
+            ClientRequest::NssGroupsByMember(id) => format!("NssGroupsByMember({})", id),
             ClientRequest::PamAuthenticateInit { account_id, info } => format!(
                 "PamAuthenticateInit{{ account_id={} tty={} pam_secvice{} rhost={} }}",
                 account_id,
diff --git a/unix_integration/resolver/src/bin/kanidm_unixd.rs b/unix_integration/resolver/src/bin/kanidm_unixd.rs
index 414554488..490ec34de 100644
--- a/unix_integration/resolver/src/bin/kanidm_unixd.rs
+++ b/unix_integration/resolver/src/bin/kanidm_unixd.rs
@@ -275,6 +275,14 @@ async fn handle_client(
                     error!("unable to load group, returning empty.");
                     ClientResponse::NssGroup(None)
                 }),
+            ClientRequest::NssGroupsByMember(account_id) => cachelayer
+                .get_nssgroups_member_name(account_id.as_str())
+                .await
+                .map(ClientResponse::NssGroups)
+                .unwrap_or_else(|_| {
+                    error!("unable to enum groups");
+                    ClientResponse::NssGroups(Vec::new())
+                }),
             ClientRequest::PamAuthenticateInit { account_id, info } => {
                 match &pam_auth_session_state {
                     Some(_auth_session) => {
diff --git a/unix_integration/resolver/src/resolver.rs b/unix_integration/resolver/src/resolver.rs
index d892086d8..ecb3167fe 100644
--- a/unix_integration/resolver/src/resolver.rs
+++ b/unix_integration/resolver/src/resolver.rs
@@ -736,6 +736,24 @@ impl Resolver {
         Ok(r)
     }
 
+    pub async fn get_nssgroups_member(&self, account_id: Id) -> Result<Vec<NssGroup>, ()> {
+        let account = self.get_nssaccount(account_id).await?;
+        if let Some(account) = account {
+            Ok(self.get_nssgroups().await.
+                unwrap_or_else(|_| Vec::new())
+                .into_iter()
+                .filter(|g| g.members.contains(&account.name))
+                .collect())
+        } else {
+            Ok(Vec::new())
+        }
+    }
+
+    #[instrument(level = "debug", skip(self))]
+    pub async fn get_nssgroups_member_name(&self, account_id: &str) -> Result<Vec<NssGroup>, ()> {
+        self.get_nssgroups_member(Id::Name(account_id.to_string())).await
+    }
+
     async fn get_nssgroup(&self, grp_id: Id) -> Result<Option<NssGroup>, ()> {
         if let Some(mut nss_group) = self.system_provider.get_nssgroup(&grp_id).await {
             debug!("system provider satisfied request");

From 8af51175f54572f57d208fd6f516c754fa77c6e3 Mon Sep 17 00:00:00 2001
From: Doridian <git@doridian.net>
Date: Sun, 29 Dec 2024 21:46:24 -0800
Subject: [PATCH 2/4] Implement libnss side possibly

---
 unix_integration/nss_kanidm/src/core.rs  | 36 ++++++++++++++++++++++++
 unix_integration/nss_kanidm/src/hooks.rs | 14 +++++++++
 2 files changed, 50 insertions(+)

diff --git a/unix_integration/nss_kanidm/src/core.rs b/unix_integration/nss_kanidm/src/core.rs
index d60ba9839..3b067a72e 100644
--- a/unix_integration/nss_kanidm/src/core.rs
+++ b/unix_integration/nss_kanidm/src/core.rs
@@ -285,6 +285,42 @@ pub fn get_group_entry_by_name(name: String, req_options: RequestOptions) -> Res
     }
 }
 
+pub fn get_group_entries_by_member(member: String, req_options: RequestOptions) -> Response<Vec<Group>> {
+    match req_options.connect_to_daemon() {
+        Source::Daemon(mut daemon_client) => {
+            let req = ClientRequest::NssGroupsByMember(member);
+            daemon_client
+                .call_and_wait(&req, None)
+                .map(|r| match r {
+                    ClientResponse::NssGroups(l) => {
+                        l.into_iter().map(group_from_nssgroup).collect()
+                    }
+                    _ => Vec::new(),
+                })
+                .map(Response::Success)
+                .unwrap_or_else(|_| Response::Success(vec![]))
+        }
+        Source::Fallback { users: _, groups } => {
+            if groups.is_empty() {
+                return Response::Unavail;
+            }
+
+            let membergroups = groups
+                .into_iter()
+                .filter_map(|etcgroup| {
+                    if etcgroup.members.contains(&member) {
+                        Some(group_from_etcgroup(etcgroup))
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            Response::Success(membergroups)
+        }
+    }
+}
+
 fn passwd_from_etcuser(etc: EtcUser) -> Passwd {
     Passwd {
         name: etc.name,
diff --git a/unix_integration/nss_kanidm/src/hooks.rs b/unix_integration/nss_kanidm/src/hooks.rs
index 62386c987..5b7968438 100644
--- a/unix_integration/nss_kanidm/src/hooks.rs
+++ b/unix_integration/nss_kanidm/src/hooks.rs
@@ -3,6 +3,7 @@ use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
 use libnss::group::{Group, GroupHooks};
 use libnss::interop::Response;
 use libnss::passwd::{Passwd, PasswdHooks};
+use libnss::initgroups::{InitgroupsHooks};
 
 struct KanidmPasswd;
 libnss_passwd_hooks!(kanidm, KanidmPasswd);
@@ -61,3 +62,16 @@ impl GroupHooks for KanidmGroup {
         core::get_group_entry_by_name(name, req_opt)
     }
 }
+
+struct KanidmInitgroups;
+libnss_initgroups_hooks!(kanidm, KanidmInitgroups);
+
+impl InitgroupsHooks for KanidmInitgroups {
+    fn get_entries_by_user(user: String) -> Response<Vec<Group>> {
+        let req_opt = RequestOptions::Main {
+            config_path: DEFAULT_CONFIG_PATH,
+        };
+
+        core::get_group_entries_by_member(user, req_opt)
+    }
+}

From 15410a783000760c6402537e9d78d97a216d3a9f Mon Sep 17 00:00:00 2001
From: Doridian <git@doridian.net>
Date: Mon, 30 Dec 2024 00:04:02 -0800
Subject: [PATCH 3/4] Simplify logic

---
 unix_integration/resolver/src/resolver.rs | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)

diff --git a/unix_integration/resolver/src/resolver.rs b/unix_integration/resolver/src/resolver.rs
index ecb3167fe..c16b9c942 100644
--- a/unix_integration/resolver/src/resolver.rs
+++ b/unix_integration/resolver/src/resolver.rs
@@ -736,22 +736,13 @@ impl Resolver {
         Ok(r)
     }
 
-    pub async fn get_nssgroups_member(&self, account_id: Id) -> Result<Vec<NssGroup>, ()> {
-        let account = self.get_nssaccount(account_id).await?;
-        if let Some(account) = account {
-            Ok(self.get_nssgroups().await.
-                unwrap_or_else(|_| Vec::new())
-                .into_iter()
-                .filter(|g| g.members.contains(&account.name))
-                .collect())
-        } else {
-            Ok(Vec::new())
-        }
-    }
-
-    #[instrument(level = "debug", skip(self))]
     pub async fn get_nssgroups_member_name(&self, account_id: &str) -> Result<Vec<NssGroup>, ()> {
-        self.get_nssgroups_member(Id::Name(account_id.to_string())).await
+        let account_name = account_id.to_string();
+        Ok(self.get_nssgroups().await.
+            unwrap_or_else(|_| Vec::new())
+            .into_iter()
+            .filter(|g| g.members.contains(&account_name))
+            .collect())
     }
 
     async fn get_nssgroup(&self, grp_id: Id) -> Result<Option<NssGroup>, ()> {

From 998e56d6482006aef5f6bf8e9c6896e5b2262f1a Mon Sep 17 00:00:00 2001
From: Doridian <git@doridian.net>
Date: Tue, 7 Jan 2025 17:09:31 -0800
Subject: [PATCH 4/4] begin reworking

---
 unix_integration/resolver/src/db.rs       | 31 +++++++++++++++++++++++
 unix_integration/resolver/src/resolver.rs | 25 +++++++++++++-----
 2 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/unix_integration/resolver/src/db.rs b/unix_integration/resolver/src/db.rs
index 094562f97..3070ac57a 100644
--- a/unix_integration/resolver/src/db.rs
+++ b/unix_integration/resolver/src/db.rs
@@ -792,6 +792,37 @@ impl DbTxn<'_> {
         }
     }
 
+    pub fn get_user_groups(&mut self, a_uuid: Uuid) -> Result<Vec<GroupToken>, CacheError> {
+        let mut stmt = self
+            .conn
+            .prepare("SELECT group_t.token FROM (group_t, memberof_t) WHERE group_t.uuid = memberof_t.g_uuid AND memberof_t.a_uuid = :a_uuid")
+            .map_err(|e| {
+                self.sqlite_error("select prepare", &e)
+            })?;
+
+        let data_iter = stmt
+            .query_map([a_uuid.as_hyphenated().to_string()], |row| row.get(0))
+            .map_err(|e| self.sqlite_error("query_map", &e))?;
+        let data: Result<Vec<Vec<u8>>, _> = data_iter
+            .map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
+            .collect();
+
+        let data = data?;
+
+        Ok(data
+            .iter()
+            .filter_map(|token| {
+                // token convert with json.
+                // trace!("{:?}", token);
+                serde_json::from_slice(token.as_slice())
+                    .map_err(|e| {
+                        error!("json error -> {:?}", e);
+                    })
+                    .ok()
+            })
+            .collect())
+    }
+
     pub fn get_group_members(&mut self, g_uuid: Uuid) -> Result<Vec<UserToken>, CacheError> {
         let mut stmt = self
             .conn
diff --git a/unix_integration/resolver/src/resolver.rs b/unix_integration/resolver/src/resolver.rs
index c16b9c942..12f55ed1f 100644
--- a/unix_integration/resolver/src/resolver.rs
+++ b/unix_integration/resolver/src/resolver.rs
@@ -576,6 +576,17 @@ impl Resolver {
         })
     }
 
+    async fn get_usergroups(&self, g_uuid: Uuid) -> Vec<String> {
+        let mut dbtxn = self.db.write().await;
+
+        dbtxn
+            .get_user_groups(g_uuid)
+            .unwrap_or_else(|_| Vec::new())
+            .into_iter()
+            .map(|gt| self.token_gidattr(&gt))
+            .collect()
+    }
+
     async fn get_groupmembers(&self, g_uuid: Uuid) -> Vec<String> {
         let mut dbtxn = self.db.write().await;
 
@@ -737,12 +748,14 @@ impl Resolver {
     }
 
     pub async fn get_nssgroups_member_name(&self, account_id: &str) -> Result<Vec<NssGroup>, ()> {
-        let account_name = account_id.to_string();
-        Ok(self.get_nssgroups().await.
-            unwrap_or_else(|_| Vec::new())
-            .into_iter()
-            .filter(|g| g.members.contains(&account_name))
-            .collect())
+        if let Some(nss_user) = self.get_nssaccount(&account_id).await {
+            Ok(self.get_usergroups(nss_user).await
+                .into_iter()
+                .map(|g| self.token_gidattr(&g))
+                .collect())
+        } else {
+            Ok(Vec::new())
+        }
     }
 
     async fn get_nssgroup(&self, grp_id: Id) -> Result<Option<NssGroup>, ()> {