From 1cda1af87241aaef4dacc55db1569b9e27671e1a Mon Sep 17 00:00:00 2001
From: Dominik George <dominik.george@teckids.org>
Date: Tue, 11 May 2021 00:45:21 +0200
Subject: [PATCH] [NSS] Implement conversion and return of passwd results

---
 src/nss.rs   | 74 +++++++++++++++++++++++++++++++++++++++-------------
 src/oauth.rs |  2 +-
 2 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/src/nss.rs b/src/nss.rs
index 57183d9..9982492 100644
--- a/src/nss.rs
+++ b/src/nss.rs
@@ -26,6 +26,8 @@ use crate::logging::setup_log;
 use crate::oauth::get_data;
 use std::collections::HashMap;
 use serde_json::value::Value;
+use std::fmt;
+use std::convert::TryInto;
 
 use libc::{getpwuid, geteuid};
 use std::ffi::CStr;
@@ -55,6 +57,51 @@ fn get_current_user() -> String {
     euser.to_str().ok().unwrap().to_string()
 }
 
+// FIXME Provide more specific error types and messages
+#[derive(Debug, Clone)]
+struct TransformMappingError {
+    msg: String,
+    field: String
+}
+impl fmt::Display for TransformMappingError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Invalid mapping configuration: field={} - {}", self.field, self.msg)
+    }
+}
+
+fn transform_ent(conf: &Config, row: &mut HashMap<String, Value>, map_name: &str) -> Result<(), TransformMappingError> {
+    let mapping: HashMap<String, HashMap<String, Value>> = get_optional(&conf, &("nss.maps.".to_string() + map_name)).unwrap_or_default();
+
+    for (field, rule) in &mapping {
+        let type_: String = rule.get("type").ok_or(TransformMappingError { field: field.to_string(), msg: "No type".to_string() })?.as_str().unwrap().to_string();
+        let value: &Value = rule.get("value").ok_or(TransformMappingError { field: field.to_string(), msg: "No value".to_string() })?;
+
+        if type_ == "static" {
+            row.insert(field.to_string(), value.clone());
+        } else if type_ == "rename" {
+            let old_value: Value = row.remove(&value.as_str().unwrap().to_string()).ok_or(TransformMappingError { field: field.to_string(), msg: "No value to rename".to_string() })?;
+            row.insert(field.to_string(), old_value);
+        } else {
+            return Err(TransformMappingError { field: field.to_string(), msg: (&("Unknown type ".to_string() + &type_)).to_string() });
+        };
+    }
+
+    Ok(())
+}
+
+fn map_to_passwd(conf: &Config, row: &mut HashMap<String, Value>) -> Result<Passwd, TransformMappingError> {
+    transform_ent(&conf, row, "passwd")?;
+    Ok(Passwd {
+        name: row.get("name").ok_or(TransformMappingError { field: "name".to_string(), msg: "No value in JSON data".to_string() })?.as_str().unwrap().to_string(),
+        passwd: row.get("passwd").ok_or(TransformMappingError { field: "passwd".to_string(), msg: "No value in JSON data".to_string() })?.as_str().unwrap().to_string(),
+        uid: row.get("uid").ok_or(TransformMappingError { field: "uid".to_string(), msg: "No value in JSON data".to_string() })?.as_u64().ok_or(TransformMappingError { field: "uid".to_string(), msg: "Invalid integer".to_string() })?.try_into().or(Err(TransformMappingError { field: "uid".to_string(), msg: "Overflow converting to u32".to_string() }))?,
+        gid: row.get("gid").ok_or(TransformMappingError { field: "gid".to_string(), msg: "No value in JSON data".to_string() })?.as_u64().ok_or(TransformMappingError { field: "gid".to_string(), msg: "Invalid integer".to_string() })?.try_into().or(Err(TransformMappingError { field: "gid".to_string(), msg: "Overflow converting to u32".to_string() }))?,
+        gecos: row.get("gecos").ok_or(TransformMappingError { field: "gecos".to_string(), msg: "No value in JSON data".to_string() })?.as_str().unwrap().to_string(),
+        dir: row.get("dir").ok_or(TransformMappingError { field: "dir".to_string(), msg: "No value in JSON data".to_string() })?.as_str().unwrap().to_string(),
+        shell: row.get("shell").ok_or(TransformMappingError { field: "shell".to_string(), msg: "No value in JSON data".to_string() })?.as_str().unwrap().to_string(),
+    })
+}
+
 struct OidcPasswd;
 
 impl PasswdHooks for OidcPasswd {
@@ -72,29 +119,20 @@ impl PasswdHooks for OidcPasswd {
             }
         };
 
-        let data: Vec<HashMap<String, Value>> = match get_data(conf, "nss", "passwd", token, "") {
+        let mut data: Vec<HashMap<String, Value>> = match get_data(&conf, "nss", "passwd", token, "") {
             Ok(d) => d,
             Err(_) => return Response::Unavail
         };
-        for ent in &data {
-            for (k, v) in ent {
-                debug!("{} {}", k, v);
-            }
+
+        let mut passwd_vec: Vec<Passwd> = Vec::new();
+        for row in &mut data {
+            match map_to_passwd(&conf, row) {
+                Ok(p) => passwd_vec.push(p),
+                Err(e) => error!("Error converting JSON to passwd entry: {}", e)
+            };
         }
 
-        Response::Success(
-            vec![
-                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::Success(passwd_vec)
     }
 
     fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> {
diff --git a/src/oauth.rs b/src/oauth.rs
index 58c8490..71e8c04 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -133,7 +133,7 @@ pub fn get_access_token_password<E: Copy>(conf: Config, prefix: &str, username:
         }
 }
 
-pub fn get_data<T: for<'de> Deserialize<'de>, E: Copy>(conf: Config, prefix: &str, endpoint: &str, token: &BasicTokenResponse, error_value: E) -> Result<T, E> {
+pub fn get_data<T: for<'de> Deserialize<'de>, E: Copy>(conf: &Config, prefix: &str, endpoint: &str, token: &BasicTokenResponse, error_value: E) -> Result<T, E> {
     let access_token = token.access_token().secret();
     let endpoint_url: String = get_or_error(&conf, &full_key(prefix, &(endpoint.to_string() + "_url")), error_value)?;
 
-- 
GitLab