From 2a8d98229f8106895e246e25dcb51b4fc3cb86d4 Mon Sep 17 00:00:00 2001
From: Dominik George <dominik.george@teckids.org>
Date: Tue, 11 May 2021 18:18:38 +0200
Subject: [PATCH] [NSS] Implement by_uid and by_name for passwd

---
 etc/nss_pam_oidc.example.toml |  4 +-
 src/nss.rs                    | 90 ++++++++++++++++++++++++-----------
 src/oauth.rs                  | 10 ++--
 3 files changed, 72 insertions(+), 32 deletions(-)

diff --git a/etc/nss_pam_oidc.example.toml b/etc/nss_pam_oidc.example.toml
index 663c74f..a1d6291 100644
--- a/etc/nss_pam_oidc.example.toml
+++ b/etc/nss_pam_oidc.example.toml
@@ -10,7 +10,9 @@ client_secret = ""
 client_id = "z8Oz0tG56QRo9QEPUZTs5Eda410FMiJtYxlInxKE"
 client_secret = ""
 
-urls.passwd = "https://ticdesk-dev.teckids.org/app/nis/api/passwd/"
+urls.passwd.list = "https://ticdesk-dev.teckids.org/app/nis/api/passwd/"
+urls.passwd.by_uid = "https://ticdesk-dev.teckids.org/app/nis/api/passwd/{}/"
+urls.passwd.by_name = "https://ticdesk-dev.teckids.org/app/nis/api/passwd/{}/"
 
 # The following configuration maps the attributes as returned by AlekSIS, as
 # example onto a system that also has local accounts (thus mapping IDs and
diff --git a/src/nss.rs b/src/nss.rs
index c4be7f1..9d76fce 100644
--- a/src/nss.rs
+++ b/src/nss.rs
@@ -94,7 +94,7 @@ impl PasswdHooks for OidcPasswd {
             }
         };
 
-        let data: Vec<PasswdHelper> = match get_data_jq(&conf, "nss", "passwd", &token, true) {
+        let data: Vec<PasswdHelper> = match get_data_jq(&conf, "nss", "passwd.list", "".to_string(), &token, true) {
             Ok(d) => d,
             Err(_) => {
                 error!("Could not load JSON data for passwd");
@@ -105,35 +105,71 @@ impl PasswdHooks for OidcPasswd {
     }
 
     fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> {
-        if uid == 1005 {
-            return Response::Success(Passwd {
-                name: "test".to_string(),
-                passwd: "x".to_string(),
-                uid: 1005,
-                gid: 1005,
-                gecos: "Test Account".to_string(),
-                dir: "/home/test".to_string(),
-                shell: "/bin/bash".to_string(),
-            });
-        }
-
-        Response::NotFound
+        let conf = nss_hook_prepare();
+        let mut cache = get_cache();
+
+        let user = get_current_user();
+        let ctc;
+        let token = match cache.load_user_token(&user) {
+            Some(t) => t,
+            None => {
+                // FIXME Implement caching of system token
+                debug!("Could not find a user token for {} to request NSS data; trying client credentials", user);
+                match get_access_token_client(&conf, "nss", "", "") {
+                    Ok(ct) => {
+                        ctc = ct.clone();
+                        &ctc
+                    },
+                    Err(_) => {
+                        error!("Failed to get access token with client credentials");
+                        return Response::Unavail;
+                    }
+                }
+            }
+        };
+
+        let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid.to_string(), &token, false).map(|PasswdHelper(p)| p) {
+            Ok(d) => d,
+            Err(_) => {
+                error!("Could not load JSON data for passwd");
+                return Response::NotFound;
+            }
+        };
+        Response::Success(data)
     }
 
     fn get_entry_by_name(name: String) -> Response<Passwd> {
-        if name == "test" {
-            return Response::Success(Passwd {
-                name: "test".to_string(),
-                passwd: "x".to_string(),
-                uid: 1005,
-                gid: 1005,
-                gecos: "Test Account".to_string(),
-                dir: "/home/test".to_string(),
-                shell: "/bin/bash".to_string(),
-            });
-        }
-
-        Response::NotFound
+        let conf = nss_hook_prepare();
+        let mut cache = get_cache();
+
+        let user = get_current_user();
+        let ctc;
+        let token = match cache.load_user_token(&user) {
+            Some(t) => t,
+            None => {
+                // FIXME Implement caching of system token
+                debug!("Could not find a user token for {} to request NSS data; trying client credentials", user);
+                match get_access_token_client(&conf, "nss", "", "") {
+                    Ok(ct) => {
+                        ctc = ct.clone();
+                        &ctc
+                    },
+                    Err(_) => {
+                        error!("Failed to get access token with client credentials");
+                        return Response::Unavail;
+                    }
+                }
+            }
+        };
+
+        let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_name", name, &token, false).map(|PasswdHelper(p)| p) {
+            Ok(d) => d,
+            Err(_) => {
+                error!("Could not load JSON data for passwd");
+                return Response::NotFound;
+            }
+        };
+        Response::Success(data)
     }
 }
 
diff --git a/src/oauth.rs b/src/oauth.rs
index 14d1882..c6f913c 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -136,9 +136,11 @@ pub fn get_access_token_password<E: Copy>(conf: &Config, prefix: &str, username:
         }
 }
 
-fn get_data(conf: &Config, prefix: &str, endpoint: &str, token: &BasicTokenResponse) -> Result<String, Box<dyn error::Error>> {
+fn get_data(conf: &Config, prefix: &str, endpoint: &str, param: String, token: &BasicTokenResponse) -> Result<String, Box<dyn error::Error>> {
     let access_token = token.access_token().secret();
-    let endpoint_url: String = get_or_error(&conf, &full_key(vec![prefix, "urls", endpoint]), "")?;
+
+    let mut endpoint_url: String = get_or_error(&conf, &full_key(vec![prefix, "urls", endpoint]), "")?;
+    endpoint_url = endpoint_url.replace("{}", &param);
 
     debug!("Loading text data from {}", endpoint_url);
     let client = reqwest::blocking::Client::new();
@@ -149,7 +151,7 @@ fn get_data(conf: &Config, prefix: &str, endpoint: &str, token: &BasicTokenRespo
         .text()?)
 }
 
-pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, endpoint: &str, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> {
+pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, endpoint: &str, param: String, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> {
     let res: Option<String> = get_optional(&conf, &full_key(vec![prefix, "maps", endpoint]));
     let jq_code = match res {
         Some(s) => match multi {
@@ -160,7 +162,7 @@ pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, en
     };
     let mut jq_prog = jq_rs::compile(&jq_code)?;
 
-    let data_raw = get_data(&conf, prefix, endpoint, token)?;
+    let data_raw = get_data(&conf, prefix, endpoint, param, token)?;
     let data_trans = jq_prog.run(&data_raw)?;
 
     Ok(serde_json::from_str(&data_trans)?)
-- 
GitLab