"serde_json",
"sock",
"tdb",
+ "tempfile",
"tokio",
"tokio-util",
"version",
[build-dependencies]
version = { path = "../version" }
+
+[dev-dependencies]
+tempfile = "3.12.0"
--- /dev/null
+fn main() {
+ println!("cargo:rustc-env=LD_LIBRARY_PATH=../../bin/shared:../../bin/shared/private/");
+}
}
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct UserEntry {
pub(crate) upn: String,
pub(crate) uuid: String,
}
}
+#[cfg(test)]
+impl GroupEntry {
+ pub fn new(uuid: &str) -> Self {
+ GroupEntry {
+ uuid: uuid.to_string(),
+ members: HashSet::new(),
+ }
+ }
+}
+
pub(crate) struct GroupCache {
cache: BasicCache,
}
pub(crate) fn merge_groups(
&mut self,
member: &str,
- entries: Vec<GroupEntry>,
+ mut entries: Vec<GroupEntry>,
) -> Result<(), Box<NTSTATUS>> {
// We need to ensure the member is removed from non-intersecting
// groups, otherwise we don't honor group membership removals.
}
}
+ // Ensure the member is added to the listed groups
+ for group in &mut entries {
+ group.add_member(member);
+ }
+
// Now add the new entries, merging with existing memberships
for group in entries {
match self.cache.fetch::<GroupEntry>(&group.uuid) {
self.cache.store_bytes(&device_id_tag, device_id.as_bytes())
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use kanidm_hsm_crypto::soft::SoftTpm;
+ use std::str::FromStr;
+ use tempfile::tempdir;
+
+ #[test]
+ fn test_basic_cache_new() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let cache = BasicCache::new(cache_path.to_str().unwrap());
+ assert!(cache.is_ok());
+ }
+
+ #[test]
+ fn test_basic_cache_store_fetch_str() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let mut cache = BasicCache::new(cache_path.to_str().unwrap()).unwrap();
+
+ let key = "test_key";
+ let value = "test_value";
+ cache.store_bytes(key, value.as_bytes()).unwrap();
+ let fetched_value = cache.fetch_str(key).unwrap();
+ assert_eq!(fetched_value, value);
+ }
+
+ #[test]
+ fn test_basic_cache_store_fetch() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let mut cache = BasicCache::new(cache_path.to_str().unwrap()).unwrap();
+
+ let key = "test_key";
+ let value = UserEntry {
+ upn: "user@test.com".to_string(),
+ uuid: "f63a43c7-b783-4da9-acb4-89f8ebfc49e9".to_string(),
+ name: "Test User".to_string(),
+ };
+
+ cache.store(key, &value).unwrap();
+ let fetched_value: Option<UserEntry> = cache.fetch(key);
+ assert!(fetched_value.is_some());
+ let fetched_value = fetched_value.unwrap();
+ assert_eq!(fetched_value.upn, value.upn);
+ assert_eq!(fetched_value.uuid, value.uuid);
+ assert_eq!(fetched_value.name, value.name);
+ }
+
+ #[test]
+ fn test_user_cache_store_fetch() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let mut cache = UserCache::new(cache_path.to_str().unwrap()).unwrap();
+
+ let entry = UserEntry {
+ upn: "user@test.com".to_string(),
+ uuid: "f63a43c7-b783-4da9-acb4-89f8ebfc49e9".to_string(),
+ name: "Test User".to_string(),
+ };
+
+ cache.store(entry.clone()).unwrap();
+ let fetched_entry = cache.fetch(&entry.upn);
+ assert!(fetched_entry.is_some());
+ let fetched_entry = fetched_entry.unwrap();
+ assert_eq!(fetched_entry.upn, entry.upn);
+ assert_eq!(fetched_entry.uuid, entry.uuid);
+ assert_eq!(fetched_entry.name, entry.name);
+ }
+
+ #[test]
+ fn test_uid_cache_store_fetch() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let mut cache = UidCache::new(cache_path.to_str().unwrap()).unwrap();
+
+ let uid: uid_t = 1000;
+ let upn = "user@test.com";
+
+ cache.store(uid, upn).unwrap();
+ let fetched_upn = cache.fetch(uid);
+ assert!(fetched_upn.is_some());
+ assert_eq!(fetched_upn.unwrap(), upn);
+ }
+
+ #[test]
+ fn test_group_cache_store_fetch() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let mut cache = GroupCache::new(cache_path.to_str().unwrap()).unwrap();
+
+ let mut group = GroupEntry {
+ uuid: "5f8be63a-a379-4324-9f42-9ea40bed9d7f".to_string(),
+ members: HashSet::new(),
+ };
+ group.add_member("user@test.com");
+
+ cache.cache.store(&group.uuid, &group).unwrap();
+ let fetched_group = cache.fetch(&group.uuid);
+ assert!(fetched_group.is_some());
+ let fetched_group = fetched_group.unwrap();
+ assert_eq!(fetched_group.uuid, group.uuid);
+ assert!(fetched_group.members.contains("user@test.com"));
+ }
+
+ #[test]
+ fn test_private_cache_loadable_machine_key_fetch_or_create() {
+ let dir = tempdir().unwrap();
+ let cache_path = dir.path().join("test.tdb");
+ let mut cache =
+ PrivateCache::new(cache_path.to_str().unwrap()).unwrap();
+
+ let mut hsm = BoxedDynTpm::new(SoftTpm::new());
+ let auth_str = AuthValue::generate().expect("Failed to create hex pin");
+ let auth_value = AuthValue::from_str(&auth_str)
+ .expect("Unable to create auth value");
+
+ let result =
+ cache.loadable_machine_key_fetch_or_create(&mut hsm, &auth_value);
+ assert!(result.is_ok());
+
+ let fetched_key = cache
+ .cache
+ .fetch::<LoadableMachineKey>("loadable_machine_key");
+ assert!(fetched_key.is_some());
+ }
+}
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::cache::{GroupCache, PrivateCache, UidCache, UserCache};
+#[cfg(not(test))]
use crate::himmelblaud::himmelblaud_pam_auth::AuthSession;
use bytes::{BufMut, BytesMut};
use dbg::{DBG_DEBUG, DBG_ERR, DBG_WARNING};
use tokio::sync::Mutex;
use tokio_util::codec::{Decoder, Encoder, Framed};
+#[cfg(not(test))]
pub(crate) struct Resolver {
realm: String,
tenant_id: String,
client: Arc<Mutex<BrokerClientApplication>>,
}
+#[cfg(not(test))]
impl Resolver {
pub(crate) fn new(
realm: &str,
}
}
+// The test environment is unable to communicate with Entra ID, therefore
+// we alter the resolver to only test the cache interactions.
+
+#[cfg(test)]
+pub(crate) struct Resolver {
+ realm: String,
+ tenant_id: String,
+ lp: LoadParm,
+ idmap: Idmap,
+ pcache: PrivateCache,
+ user_cache: UserCache,
+ uid_cache: UidCache,
+ group_cache: GroupCache,
+}
+
+#[cfg(test)]
+impl Resolver {
+ pub(crate) fn new(
+ realm: &str,
+ tenant_id: &str,
+ lp: LoadParm,
+ idmap: Idmap,
+ pcache: PrivateCache,
+ user_cache: UserCache,
+ uid_cache: UidCache,
+ group_cache: GroupCache,
+ ) -> Self {
+ Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ }
+ }
+}
+
struct ClientCodec;
impl Decoder for ClientCodec {
};
let mut reqs = Framed::new(stream, ClientCodec::new());
+ #[cfg(not(test))]
let mut pam_auth_session_state = None;
while let Some(Ok(req)) = reqs.next().await {
let mut resolver = resolver.lock().await;
let resp = match req {
+ #[cfg(not(test))]
Request::PamAuthenticateInit(account_id) => {
DBG_DEBUG!("pam authenticate init");
}
}
}
+ #[cfg(not(test))]
Request::PamAuthenticateStep(pam_next_req) => {
DBG_DEBUG!("pam authenticate step");
match &mut pam_auth_session_state {
resolver.getgrnam(&grp_id).await?
}
Request::NssGroupByGid(gid) => resolver.getgrgid(gid).await?,
+ #[cfg(not(test))]
Request::PamAccountAllowed(account_id) => {
resolver.pam_acct_mgmt(&account_id).await?
}
Request::PamAccountBeginSession(_account_id) => Response::Success,
+ #[cfg(test)]
+ _ => Response::Error,
};
reqs.send(resp).await?;
reqs.flush().await?;
mod himmelblaud_getpwent;
mod himmelblaud_getpwnam;
mod himmelblaud_getpwuid;
+#[cfg(not(test))]
mod himmelblaud_pam_acct_mgmt;
+#[cfg(not(test))]
mod himmelblaud_pam_auth;
let name = entry.uuid.clone();
let gid = self
.idmap
- .gen_to_unix(&self.tenant_id, &entry.uuid.to_uppercase())
+ .gen_to_unix(&self.tenant_id, &entry.uuid)
.map_err(|e| {
DBG_ERR!("{:?}", e);
Box::new(NT_STATUS_NO_SUCH_GROUP)
Ok(Response::NssGroups(res))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::cache::GroupEntry;
+ use crate::{GroupCache, PrivateCache, UidCache, UserCache};
+ use idmap::Idmap;
+ use param::LoadParm;
+ use std::collections::HashSet;
+ use tempfile::tempdir;
+
+ #[tokio::test]
+ async fn test_getgrent() {
+ // Create a temporary directory for the cache
+ let dir = tempdir().unwrap();
+
+ // Initialize the caches
+ let private_cache_path = dir
+ .path()
+ .join("himmelblau.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let pcache = PrivateCache::new(&private_cache_path).unwrap();
+ let user_cache_path = dir
+ .path()
+ .join("himmelblau_users.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let user_cache = UserCache::new(&user_cache_path).unwrap();
+ let uid_cache_path = dir
+ .path()
+ .join("uid_cache.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let uid_cache = UidCache::new(&uid_cache_path).unwrap();
+ let group_cache_path = dir
+ .path()
+ .join("himmelblau_groups.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut group_cache = GroupCache::new(&group_cache_path).unwrap();
+
+ // Insert dummy GroupEntries into the cache
+ let group_uuid1 = "c490c3ea-fd98-4d45-b6aa-2a3520f804fa";
+ let group_uuid2 = "f7a51b58-84de-42a3-b5b1-967b17c04f89";
+ let dummy_group1 = GroupEntry::new(group_uuid1);
+ let dummy_group2 = GroupEntry::new(group_uuid2);
+ group_cache
+ .merge_groups("user1@test.com", vec![dummy_group1.clone()])
+ .unwrap();
+ group_cache
+ .merge_groups("user2@test.com", vec![dummy_group2.clone()])
+ .unwrap();
+
+ // Initialize the Idmap with dummy configuration
+ let realm = "test.com";
+ let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
+ let mut idmap = Idmap::new().unwrap();
+ idmap
+ .add_gen_domain(realm, tenant_id, (1000, 2000))
+ .unwrap();
+
+ // Initialize dummy configuration
+ let lp = LoadParm::new(None).expect("Failed loading default config");
+
+ // Initialize the Resolver
+ let mut resolver = Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ };
+
+ // Test the getgrent function
+ let result = resolver.getgrent().await.unwrap();
+
+ match result {
+ Response::NssGroups(mut groups) => {
+ groups.sort_by(|a, b| a.name.cmp(&b.name));
+ assert_eq!(groups.len(), 2);
+
+ let group1 = &groups[0];
+ assert_eq!(group1.name, dummy_group1.uuid);
+ assert_eq!(group1.gid, 1388);
+ assert_eq!(group1.members, vec!["user1@test.com".to_string()]);
+
+ let group2 = &groups[1];
+ assert_eq!(group2.name, dummy_group2.uuid);
+ assert_eq!(group2.gid, 1593);
+ assert_eq!(group2.members, vec!["user2@test.com".to_string()]);
+ }
+ other => {
+ panic!("Expected NssGroups with a list of groups: {:?}", other)
+ }
+ }
+ }
+}
Ok(Response::NssGroup(None))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::cache::GroupEntry;
+ use crate::{GroupCache, PrivateCache, UidCache, UserCache};
+ use idmap::Idmap;
+ use param::LoadParm;
+ use std::collections::HashSet;
+ use tempfile::tempdir;
+
+ #[tokio::test]
+ async fn test_getgrgid() {
+ // Create a temporary directory for the cache
+ let dir = tempdir().unwrap();
+
+ // Initialize the caches
+ let private_cache_path = dir
+ .path()
+ .join("himmelblau.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let pcache = PrivateCache::new(&private_cache_path).unwrap();
+ let user_cache_path = dir
+ .path()
+ .join("himmelblau_users.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let user_cache = UserCache::new(&user_cache_path).unwrap();
+ let uid_cache_path = dir
+ .path()
+ .join("uid_cache.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut uid_cache = UidCache::new(&uid_cache_path).unwrap();
+ let group_cache_path = dir
+ .path()
+ .join("himmelblau_groups.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut group_cache = GroupCache::new(&group_cache_path).unwrap();
+
+ // Initialize the Idmap with dummy configuration
+ let realm = "test.com";
+ let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
+ let mut idmap = Idmap::new().unwrap();
+ idmap
+ .add_gen_domain(realm, tenant_id, (1000, 2000))
+ .unwrap();
+
+ // Insert a dummy GroupEntry into the cache
+ let group_uuid = "c490c3ea-fd98-4d45-b6aa-2a3520f804fa".to_string();
+ let dummy_gid = idmap
+ .gen_to_unix(tenant_id, &group_uuid)
+ .expect("Failed to map group gid");
+ // Store the calculated gid -> uuid map in the cache
+ uid_cache
+ .store(dummy_gid, &group_uuid)
+ .expect("Failed to store group gid");
+ let dummy_group = GroupEntry::new(&group_uuid);
+ group_cache
+ .merge_groups("user1@test.com", vec![dummy_group.clone()])
+ .unwrap();
+
+ // Initialize dummy configuration
+ let lp = LoadParm::new(None).expect("Failed loading default config");
+
+ // Initialize the Resolver
+ let mut resolver = Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ };
+
+ // Test the getgrgid function with a gid that exists
+ let result = resolver.getgrgid(dummy_gid).await.unwrap();
+
+ match result {
+ Response::NssGroup(Some(group)) => {
+ assert_eq!(group.name, dummy_group.uuid);
+ assert_eq!(group.gid, dummy_gid);
+ assert_eq!(group.members, vec!["user1@test.com".to_string()]);
+ }
+ other => panic!("Expected NssGroup with Some(group): {:?}", other),
+ }
+
+ // Test the getgrgid function with a gid that does not exist
+ let nonexistent_gid: gid_t = 1600;
+ let result = resolver.getgrgid(nonexistent_gid).await.unwrap();
+ match result {
+ Response::NssGroup(None) => {} // This is the expected result
+ _ => panic!("Expected NssGroup with None"),
+ }
+ }
+}
};
let gid = self
.idmap
- .gen_to_unix(&self.tenant_id, &entry.uuid.to_uppercase())
+ .gen_to_unix(&self.tenant_id, &entry.uuid)
.map_err(|e| {
DBG_ERR!("{:?}", e);
Box::new(NT_STATUS_INVALID_TOKEN)
})?;
// Store the calculated gid -> uuid map in the cache
- self.uid_cache.store(gid, &entry.uuid.to_uppercase())?;
+ self.uid_cache.store(gid, &entry.uuid)?;
let group = Group {
name: entry.uuid.clone(),
passwd: "x".to_string(),
return Ok(Response::NssGroup(Some(group)));
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::cache::GroupEntry;
+ use crate::{GroupCache, PrivateCache, UidCache, UserCache};
+ use idmap::Idmap;
+ use param::LoadParm;
+ use std::collections::HashSet;
+ use tempfile::tempdir;
+
+ #[tokio::test]
+ async fn test_getgrnam() {
+ // Create a temporary directory for the cache
+ let dir = tempdir().unwrap();
+
+ // Initialize the caches
+ let private_cache_path = dir
+ .path()
+ .join("himmelblau.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let pcache = PrivateCache::new(&private_cache_path).unwrap();
+ let user_cache_path = dir
+ .path()
+ .join("himmelblau_users.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let user_cache = UserCache::new(&user_cache_path).unwrap();
+ let uid_cache_path = dir
+ .path()
+ .join("uid_cache.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let uid_cache = UidCache::new(&uid_cache_path).unwrap();
+ let group_cache_path = dir
+ .path()
+ .join("himmelblau_groups.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut group_cache = GroupCache::new(&group_cache_path).unwrap();
+
+ // Insert a dummy GroupEntry into the cache
+ let group_uuid = "c490c3ea-fd98-4d45-b6aa-2a3520f804fa";
+ let dummy_group = GroupEntry::new(group_uuid);
+ group_cache
+ .merge_groups("user1@test.com", vec![dummy_group.clone()])
+ .unwrap();
+ group_cache
+ .merge_groups("user2@test.com", vec![dummy_group.clone()])
+ .unwrap();
+
+ // Initialize the Idmap with dummy configuration
+ let realm = "test.com";
+ let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
+ let mut idmap = Idmap::new().unwrap();
+ idmap
+ .add_gen_domain(realm, tenant_id, (1000, 2000))
+ .unwrap();
+
+ // Initialize dummy configuration
+ let lp = LoadParm::new(None).expect("Failed loading default config");
+
+ // Initialize the Resolver
+ let mut resolver = Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ };
+
+ // Test the getgrnam function with a group that exists
+ let result = resolver.getgrnam(group_uuid).await.unwrap();
+
+ match result {
+ Response::NssGroup(Some(mut group)) => {
+ group.members.sort();
+ assert_eq!(group.name, dummy_group.uuid);
+ assert_eq!(group.gid, 1388);
+ assert_eq!(
+ group.members,
+ vec![
+ "user1@test.com".to_string(),
+ "user2@test.com".to_string()
+ ]
+ );
+ }
+ other => panic!("Expected NssGroup with Some(group): {:?}", other),
+ }
+
+ // Test the getgrnam function with a group that does not exist
+ let nonexistent_group_uuid = "2ea8f1d4-1b94-4003-865b-cb247a8a1f5d";
+ let result = resolver.getgrnam(nonexistent_group_uuid).await.unwrap();
+ match result {
+ Response::NssGroup(None) => {} // This is the expected result
+ _ => panic!("Expected NssGroup with None"),
+ }
+ }
+}
for entry in user_entries {
let uid = self
.idmap
- .gen_to_unix(&self.tenant_id, &entry.upn.to_lowercase())
+ .gen_to_unix(&self.tenant_id, &entry.upn)
.map_err(|e| {
DBG_ERR!("{:?}", e);
Box::new(NT_STATUS_INVALID_TOKEN)
Ok(Response::NssAccounts(res))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::cache::UserEntry;
+ use crate::{GroupCache, PrivateCache, UidCache, UserCache};
+ use idmap::Idmap;
+ use param::LoadParm;
+ use std::collections::HashSet;
+ use tempfile::tempdir;
+
+ #[tokio::test]
+ async fn test_getpwent() {
+ // Create a temporary directory for the cache
+ let dir = tempdir().unwrap();
+
+ // Initialize the caches
+ let private_cache_path = dir
+ .path()
+ .join("himmelblau.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let pcache = PrivateCache::new(&private_cache_path).unwrap();
+ let user_cache_path = dir
+ .path()
+ .join("himmelblau_users.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut user_cache = UserCache::new(&user_cache_path).unwrap();
+ let uid_cache_path = dir
+ .path()
+ .join("uid_cache.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let uid_cache = UidCache::new(&uid_cache_path).unwrap();
+ let group_cache_path = dir
+ .path()
+ .join("himmelblau_groups.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let group_cache = GroupCache::new(&group_cache_path).unwrap();
+
+ // Insert dummy UserEntrys into the cache
+ let dummy_user = UserEntry {
+ upn: "user1@test.com".to_string(),
+ uuid: "731e9af3-668d-4033-afd1-9f09b9120cc7".to_string(),
+ name: "User One".to_string(),
+ };
+ user_cache
+ .store(dummy_user.clone())
+ .expect("Failed storing user in cache");
+
+ let dummy_user2 = UserEntry {
+ upn: "user2@test.com".to_string(),
+ uuid: "7be6c0c5-5763-4633-aecf-f8c460b338fd".to_string(),
+ name: "User Two".to_string(),
+ };
+ user_cache
+ .store(dummy_user2.clone())
+ .expect("Failed storing user in cache");
+
+ // Initialize the Idmap with dummy configuration
+ let realm = "test.com";
+ let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
+ let mut idmap = Idmap::new().unwrap();
+ idmap
+ .add_gen_domain(realm, tenant_id, (1000, 2000))
+ .unwrap();
+
+ // Initialize dummy configuration for LoadParm
+ let lp = LoadParm::new(None).expect("Failed loading default config");
+
+ // Initialize the Resolver
+ let mut resolver = Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ };
+
+ // Test the getpwent function
+ let result = resolver.getpwent().await.unwrap();
+
+ match result {
+ Response::NssAccounts(accounts) => {
+ assert_eq!(accounts.len(), 2);
+
+ let account1 = &accounts[0];
+ assert_eq!(account1.name, dummy_user.upn);
+ assert_eq!(account1.uid, 1316);
+ assert_eq!(account1.gid, 1316);
+ assert_eq!(account1.gecos, dummy_user.name);
+ assert_eq!(account1.dir, "/home/test.com/user1");
+ assert_eq!(account1.shell, "/bin/false");
+
+ let account2 = &accounts[1];
+ assert_eq!(account2.name, dummy_user2.upn);
+ assert_eq!(account2.uid, 1671);
+ assert_eq!(account2.gid, 1671);
+ assert_eq!(account2.gecos, dummy_user2.name);
+ assert_eq!(account2.dir, "/home/test.com/user2");
+ assert_eq!(account2.shell, "/bin/false");
+ }
+ other => panic!(
+ "Expected NssAccounts with a list of accounts: {:?}",
+ other
+ ),
+ }
+ }
+}
DBG_ERR!("Failed to discover template shell. Is it set?");
Box::new(NT_STATUS_NOT_A_DIRECTORY)
})?;
- let uid = self
- .idmap
- .gen_to_unix(&self.tenant_id, &upn.to_lowercase())
- .map_err(|e| {
+ let uid =
+ self.idmap.gen_to_unix(&self.tenant_id, &upn).map_err(|e| {
DBG_ERR!("{:?}", e);
Box::new(NT_STATUS_INVALID_TOKEN)
})?;
// based on whether the upn exists in Entra ID.
let entry = match self.user_cache.fetch(account_id) {
Some(entry) => entry,
+ #[cfg(not(test))]
None => {
// Check if the user exists in Entra ID
let exists = match self
}
return Ok(Response::NssAccount(None));
}
+ #[cfg(test)]
+ None => return Ok(Response::NssAccount(None)),
};
return Ok(Response::NssAccount(Some(
self.create_passwd_from_upn(&entry.upn, &entry.name)?,
)));
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::cache::UserEntry;
+ use crate::{GroupCache, PrivateCache, UidCache, UserCache};
+ use idmap::Idmap;
+ use param::LoadParm;
+ use tempfile::tempdir;
+
+ #[tokio::test]
+ async fn test_getpwnam() {
+ // Create a temporary directory for the cache
+ let dir = tempdir().unwrap();
+
+ // Initialize the caches
+ let private_cache_path = dir
+ .path()
+ .join("himmelblau.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let pcache = PrivateCache::new(&private_cache_path).unwrap();
+ let user_cache_path = dir
+ .path()
+ .join("himmelblau_users.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut user_cache = UserCache::new(&user_cache_path).unwrap();
+ let uid_cache_path = dir
+ .path()
+ .join("uid_cache.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let uid_cache = UidCache::new(&uid_cache_path).unwrap();
+ let group_cache_path = dir
+ .path()
+ .join("himmelblau_groups.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let group_cache = GroupCache::new(&group_cache_path).unwrap();
+
+ // Insert a dummy UserEntry into the cache
+ let dummy_user = UserEntry {
+ upn: "user1@test.com".to_string(),
+ uuid: "731e9af3-668d-4033-afd1-9f09b9120cc7".to_string(),
+ name: "User One".to_string(),
+ };
+ let _ = user_cache.store(dummy_user.clone());
+
+ // Initialize the Idmap with dummy configuration
+ let realm = "test.com";
+ let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
+ let mut idmap = Idmap::new().unwrap();
+ idmap
+ .add_gen_domain(realm, tenant_id, (1000, 2000))
+ .unwrap();
+
+ // Initialize dummy configuration for LoadParm
+ let lp = LoadParm::new(None).expect("Failed loading default config");
+
+ // Initialize the Resolver
+ let mut resolver = Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ };
+
+ // Test the getpwnam function with a user that exists in the cache
+ let result = resolver.getpwnam(&dummy_user.upn).await.unwrap();
+
+ match result {
+ Response::NssAccount(Some(account)) => {
+ assert_eq!(account.name, dummy_user.upn);
+ assert_eq!(account.uid, 1316);
+ assert_eq!(account.gid, 1316);
+ assert_eq!(account.gecos, dummy_user.name);
+ assert_eq!(account.dir, "/home/test.com/user1");
+ assert_eq!(account.shell, "/bin/false");
+ }
+ other => {
+ panic!("Expected NssAccount with Some(account): {:?}", other)
+ }
+ }
+
+ // Test the getpwnam function with a user that does not exist in the cache
+ let nonexistent_user_upn = "nonexistent@test.com";
+ let result = resolver.getpwnam(nonexistent_user_upn).await.unwrap();
+
+ match result {
+ Response::NssAccount(None) => {} // This is the expected result
+ other => panic!("Expected NssAccount with None: {:?}", other),
+ }
+ }
+}
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::cache::UserEntry;
+ use crate::{GroupCache, PrivateCache, UidCache, UserCache};
+ use idmap::Idmap;
+ use param::LoadParm;
+ use tempfile::tempdir;
+
+ #[tokio::test]
+ async fn test_getpwuid() {
+ // Create a temporary directory for the cache
+ let dir = tempdir().unwrap();
+
+ // Initialize the caches
+ let private_cache_path = dir
+ .path()
+ .join("himmelblau.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let pcache = PrivateCache::new(&private_cache_path).unwrap();
+ let user_cache_path = dir
+ .path()
+ .join("himmelblau_users.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut user_cache = UserCache::new(&user_cache_path).unwrap();
+ let uid_cache_path = dir
+ .path()
+ .join("uid_cache.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let mut uid_cache = UidCache::new(&uid_cache_path).unwrap();
+ let group_cache_path = dir
+ .path()
+ .join("himmelblau_groups.tdb")
+ .to_str()
+ .unwrap()
+ .to_string();
+ let group_cache = GroupCache::new(&group_cache_path).unwrap();
+
+ // Insert a dummy UserEntry into the cache
+ let dummy_user = UserEntry {
+ upn: "user1@test.com".to_string(),
+ uuid: "731e9af3-668d-4033-afd1-9f09b9120cc7".to_string(),
+ name: "User One".to_string(),
+ };
+ let _ = user_cache.store(dummy_user.clone());
+
+ // Initialize the Idmap with dummy configuration
+ let realm = "test.com";
+ let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
+ let mut idmap = Idmap::new().unwrap();
+ idmap
+ .add_gen_domain(realm, tenant_id, (1000, 2000))
+ .unwrap();
+
+ let uid = idmap
+ .gen_to_unix(tenant_id, &dummy_user.upn)
+ .expect("Failed to generate uid for user");
+ // Store the calculated uid -> upn map in the cache
+ uid_cache
+ .store(uid, &dummy_user.upn)
+ .expect("Failed storing generated uid in the cache");
+
+ // Initialize dummy configuration for LoadParm
+ let lp = LoadParm::new(None).expect("Failed loading default config");
+
+ // Initialize the Resolver
+ let mut resolver = Resolver {
+ realm: realm.to_string(),
+ tenant_id: tenant_id.to_string(),
+ lp,
+ idmap,
+ pcache,
+ user_cache,
+ uid_cache,
+ group_cache,
+ };
+
+ // Test the getpwuid function with a uid that exists in the cache
+ let result = resolver.getpwuid(uid).await.unwrap();
+
+ match result {
+ Response::NssAccount(Some(account)) => {
+ assert_eq!(account.name, dummy_user.upn);
+ assert_eq!(account.uid, uid);
+ assert_eq!(account.gid, uid);
+ assert_eq!(account.gecos, dummy_user.name);
+ assert_eq!(account.dir, "/home/test.com/user1");
+ assert_eq!(account.shell, "/bin/false");
+ }
+ other => {
+ panic!("Expected NssAccount with Some(account): {:?}", other)
+ }
+ }
+
+ // Test the getpwuid function with a uid that does not exist in the cache
+ let nonexistent_uid = 9999;
+ let result = resolver.getpwuid(nonexistent_uid).await.unwrap();
+
+ match result {
+ Response::NssAccount(None) => {} // This is the expected result
+ other => panic!("Expected NssAccount with None: {:?}", other),
+ }
+ }
+}
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+
+// Ignore unused/dead code when running cargo test
+#![cfg_attr(test, allow(unused_imports))]
+#![cfg_attr(test, allow(dead_code))]
+
use clap::{Arg, ArgAction, Command};
use dbg::*;
use himmelblau::graph::Graph;
use cache::{GroupCache, PrivateCache, UidCache, UserCache};
mod utils;
+#[cfg(not(test))]
#[tokio::main(flavor = "current_thread")]
async fn main() -> ExitCode {
let clap_args = Command::new("himmelblaud")
Box::new(NT_STATUS_UNSUCCESSFUL)
})
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+ use tempfile::tempdir;
+ use tokio::fs;
+
+ #[test]
+ fn test_split_username_success() {
+ let username = "user@domain.com";
+ let result = split_username(username);
+ assert!(result.is_ok());
+ let (user, domain) = result.unwrap();
+ assert_eq!(user, "user");
+ assert_eq!(domain, "domain.com");
+ }
+
+ #[test]
+ fn test_split_username_failure() {
+ let username = "invalid_username";
+ let result = split_username(username);
+ assert!(result.is_err());
+ assert_eq!(*result.unwrap_err(), NT_STATUS_INVALID_USER_PRINCIPAL_NAME);
+ }
+
+ #[tokio::test]
+ async fn test_hsm_pin_fetch_or_create_generate() {
+ let dir = tempdir().unwrap();
+ let path = dir.path().join("hsm_pin");
+
+ let result = hsm_pin_fetch_or_create(path.to_str().unwrap()).await;
+ assert!(result.is_ok());
+
+ // Verify that the file is created and contains a valid auth value
+ let saved_pin = fs::read(path).await.expect("Auth value missing");
+ AuthValue::try_from(saved_pin.as_slice())
+ .expect("Failed parsing auth value");
+ }
+
+ #[tokio::test]
+ async fn test_hsm_pin_fetch_or_create_invalid_path() {
+ let result = hsm_pin_fetch_or_create("invalid_path\0").await;
+ assert!(result.is_err());
+ match result {
+ Err(e) => assert_eq!(*e, NT_STATUS_UNSUCCESSFUL),
+ Ok(_) => panic!("Expected error but got success"),
+ }
+ }
+
+ #[tokio::test]
+ async fn test_hsm_pin_fetch_or_create_invalid_auth_value() {
+ let dir = tempdir().unwrap();
+ let path = dir.path().join("hsm_pin");
+
+ // Write invalid content to the file
+ let mut file = std::fs::File::create(&path).unwrap();
+ file.write_all(b"invalid_auth_value").unwrap();
+
+ // Test reading the invalid file
+ let result = hsm_pin_fetch_or_create(path.to_str().unwrap()).await;
+ assert!(result.is_err());
+ match result {
+ Err(e) => assert_eq!(*e, NT_STATUS_UNSUCCESSFUL),
+ Ok(_) => panic!("Expected error but got success"),
+ }
+ }
+}
use std::error::Error;
use std::ffi::c_void;
use std::fmt;
+use std::path::PathBuf;
use std::sync::{Arc, Mutex};
mod ffi {
open_flags: Option<i32>,
mode: Option<u32>,
) -> Result<Self, Box<dyn Error + '_>> {
+ let path = PathBuf::from(name);
let tdb = unsafe {
ffi::tdb_open(
wrap_string(name),
hash_size.unwrap_or(0),
tdb_flags.unwrap_or(ffi::TDB_DEFAULT as i32),
- open_flags.unwrap_or(libc::O_RDWR),
+ match open_flags {
+ Some(open_flags) => open_flags,
+ None => {
+ if path.exists() {
+ libc::O_RDWR
+ } else {
+ libc::O_RDWR | libc::O_CREAT
+ }
+ }
+ },
mode.unwrap_or(0o600),
)
};