diff --git a/examples/nsscache-scim.conf b/examples/nsscache-scim.conf index badff5f..d2e6c9e 100644 --- a/examples/nsscache-scim.conf +++ b/examples/nsscache-scim.conf @@ -1,5 +1,4 @@ # Example nsscache.conf for SCIM source -# This configuration uses CoreWeave's SCIM endpoints for testing [DEFAULT] @@ -63,6 +62,14 @@ scim_path_login_shell = urn:example:params:scim:schemas:extension:User/loginShel # (Optional) Default shell if not specified for the user scim_default_shell = /bin/bash +# (Optional) Override home directory for all users +# If set, this takes precedence over scim_path_home_directory +# Use %%u to substitute the username (optional) +# Examples: +# scim_override_home_directory = /mnt/home/%%u # Per-user: /mnt/home/john +# scim_override_home_directory = /shared/home # Same for all: /shared/home +scim_override_home_directory = /mnt/home/%%u + [sshkey] scim_path_username = urn:example:params:scim:schemas:extension:User/userName diff --git a/nss_cache/sources/ldapsource.py b/nss_cache/sources/ldapsource.py index cbe4206..d24dc75 100644 --- a/nss_cache/sources/ldapsource.py +++ b/nss_cache/sources/ldapsource.py @@ -865,7 +865,9 @@ def Transform(self, obj): pw.uid = int(pw.uid + self.conf["offset"]) pw.gid = int(pw.gid + self.conf["offset"]) - if self.conf.get("home_dir"): + if "override_home_dir" in self.conf: + pw.dir = self.conf.get("override_home_dir", "").replace("%u", pw.name) + elif self.conf.get("home_dir"): pw.dir = "/home/%s" % pw.name elif "unixHomeDirectory" in obj: pw.dir = obj["unixHomeDirectory"][0] diff --git a/nss_cache/sources/ldapsource_test.py b/nss_cache/sources/ldapsource_test.py index db35a19..a410208 100644 --- a/nss_cache/sources/ldapsource_test.py +++ b/nss_cache/sources/ldapsource_test.py @@ -1267,6 +1267,100 @@ def testVerifyRID(self): serverctrls=self.compareSPRC(), ) + def testGetPasswdMapWithHomeDirectoryOverride(self): + test_posix_account = ( + "cn=test,ou=People,dc=example,dc=com", + { + "sambaSID": ["S-1-5-21-2127521184-1604012920-1887927527-72713"], + "uidNumber": [1000], + "gidNumber": [1000], + "uid": ["test"], + "cn": ["Testguy McTest"], + "homeDirectory": ["/home/test"], + "loginShell": ["/bin/sh"], + "userPassword": ["p4ssw0rd"], + "modifyTimestamp": ["20070227012807Z"], + }, + ) + config = dict(self.config) + config["override_home_dir"] = "/mnt/home/%u" + attrlist = [ + "uid", + "uidNumber", + "gidNumber", + "gecos", + "cn", + "homeDirectory", + "loginShell", + "fullName", + "modifyTimestamp", + ] + self.ldap_mock.ReconnectLDAPObject.return_value.result3.side_effect = [ + (ldap.RES_SEARCH_ENTRY, [test_posix_account], None, []), + (ldap.RES_SEARCH_RESULT, None, None, []), + ] + + source = ldapsource.LdapSource(config) + data = source.GetPasswdMap() + + self.assertEqual(1, len(data)) + first = data.PopItem() + self.assertEqual("/mnt/home/test", first.dir) + self.ldap_mock.ReconnectLDAPObject.return_value.search_ext.assert_called_with( + base=mock.ANY, + filterstr=mock.ANY, + scope=mock.ANY, + attrlist=attrlist, + serverctrls=self.compareSPRC(), + ) + + def testGetPasswdMapWithHomeDirectoryOverrideNoSubstitution(self): + test_posix_account = ( + "cn=test,ou=People,dc=example,dc=com", + { + "sambaSID": ["S-1-5-21-2127521184-1604012920-1887927527-72713"], + "uidNumber": [1000], + "gidNumber": [1000], + "uid": ["test"], + "cn": ["Testguy McTest"], + "homeDirectory": ["/home/test"], + "loginShell": ["/bin/sh"], + "userPassword": ["p4ssw0rd"], + "modifyTimestamp": ["20070227012807Z"], + }, + ) + config = dict(self.config) + config["override_home_dir"] = "/shared/home" + attrlist = [ + "uid", + "uidNumber", + "gidNumber", + "gecos", + "cn", + "homeDirectory", + "loginShell", + "fullName", + "modifyTimestamp", + ] + self.ldap_mock.ReconnectLDAPObject.return_value.result3.side_effect = [ + (ldap.RES_SEARCH_ENTRY, [test_posix_account], None, []), + (ldap.RES_SEARCH_RESULT, None, None, []), + ] + + source = ldapsource.LdapSource(config) + data = source.GetPasswdMap() + + self.assertEqual(1, len(data)) + first = data.PopItem() + self.assertEqual("/shared/home", first.dir) + self.ldap_mock.ReconnectLDAPObject.return_value.search_ext.assert_called_with( + base=mock.ANY, + filterstr=mock.ANY, + scope=mock.ANY, + attrlist=attrlist, + serverctrls=self.compareSPRC(), + ) + class TestUpdateGetter(unittest.TestCase): def setUp(self): diff --git a/nss_cache/sources/scimsource.py b/nss_cache/sources/scimsource.py index 75a06db..5d92b58 100644 --- a/nss_cache/sources/scimsource.py +++ b/nss_cache/sources/scimsource.py @@ -511,6 +511,12 @@ def _ExtractGecos(self, user_data): def _ExtractHomeDir(self, user_data): """Extract home directory using configurable path.""" + # Check for override_home_directory first + override_home = self._GetMapConfig("override_home_directory", "") + if override_home: + username = self._ExtractUsername(user_data) + return override_home.replace("%u", username) if username else override_home + home_path = self._GetMapConfig("scim_path_home_directory", "") # Try the configured path first diff --git a/nss_cache/sources/scimsource_test.py b/nss_cache/sources/scimsource_test.py index 52025cc..1d298b9 100644 --- a/nss_cache/sources/scimsource_test.py +++ b/nss_cache/sources/scimsource_test.py @@ -578,6 +578,44 @@ def testReadEntryMissingUid(self): self.assertIsNone(entry) + def testReadEntryWithHomeDirectoryOverride(self): + """Test _ReadEntry with home directory override.""" + self.mock_source.conf.update({ + "override_home_directory": "/mnt/home/%u" + }) + user_data = { + "id": "1001", + "userName": "testuser", + "homeDirectory": "/home/testuser", + "loginShell": "/bin/bash", + "name": {"formatted": "Test User"} + } + + entry = self.parser._ReadEntry(user_data) + + self.assertIsNotNone(entry) + self.assertEqual(entry.name, "testuser") + self.assertEqual(entry.dir, "/mnt/home/testuser") # Should use override with %u substitution + + def testReadEntryWithHomeDirectoryOverrideNoSubstitution(self): + """Test _ReadEntry with home directory override without %u substitution.""" + self.mock_source.conf.update({ + "override_home_directory": "/shared/home" + }) + user_data = { + "id": "1001", + "userName": "testuser", + "homeDirectory": "/home/testuser", + "loginShell": "/bin/bash", + "name": {"formatted": "Test User"} + } + + entry = self.parser._ReadEntry(user_data) + + self.assertIsNotNone(entry) + self.assertEqual(entry.name, "testuser") + self.assertEqual(entry.dir, "/shared/home") # Should use override as-is without substitution + class TestScimSshkeyMapParser(unittest.TestCase): def setUp(self): diff --git a/nsscache.conf b/nsscache.conf index 7b9ae8f..3a2cdcc 100644 --- a/nsscache.conf +++ b/nsscache.conf @@ -155,6 +155,12 @@ files_cache_filename_suffix = cache # # ldap_base = ou=people,dc=example,dc=com +# Override home directory for all users +# If set, this takes precedence over ldap_home_dir +# Use %%u to substitute the username (optional) +# Examples: +# ldap_override_home_dir = /mnt/home/%%u # Per-user: /mnt/home/john +# ldap_override_home_dir = /shared/home # Same for all: /shared/home [group] ldap_base = ou=group,dc=example,dc=com diff --git a/nsscache.conf.5 b/nsscache.conf.5 index 7537431..1c74f1e 100644 --- a/nsscache.conf.5 +++ b/nsscache.conf.5 @@ -218,6 +218,14 @@ If specified, set every user's login shell to the given one. May be useful on bastion hosts or to ensure uniformity. Enable for Active Directory since the attribute (loginShell) is not default. +.TP +.B ldap_override_home_dir +If specified, set every user's home directory to the given value. +Use %%u to substitute the username (optional). For example, /mnt/home/%%u would +set user "john" to /mnt/home/john, while /shared/home would set all users +to the same directory. This takes precedence over any +home directory attributes and ldap_home_dir from the LDAP source. + .TP .B ldap_default_shell Set a default shell for all users if not specified. @@ -389,6 +397,14 @@ Number of retries on soft failures before giving up. Defaults to 3. Default shell to assign to users if not specified in SCIM data. Defaults to .I /bin/bash +.TP +.B scim_override_home_directory +If specified in a [passwd] section, set every user's home directory to the +given value. Use %%u to substitute the username (optional). For example, +/mnt/home/%%u would set user "john" to /mnt/home/john, while /shared/home +would set all users to the same directory. This takes precedence over any +scim_path_home_directory configuration and SCIM response data. + The following path configuration options allow customization of how data is extracted from SCIM responses. These can be set per-map in [passwd], [group], or [sshkey] sections: