def test_pso_invalid_location(self): """Tests that PSOs in an invalid location have no effect""" # PSOs should only be able to be created within a Password Settings # Container object. Trying to create one under an OU should fail try: rogue_pso = PasswordSettings("rogue-PSO", self.ldb, precedence=1, complexity=False, password_len=20, container=self.ou) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_NAMING_VIOLATION, msg) # Windows returns 2099 (Illegal superior), Samba returns 2037 # (Naming violation - "not a valid child class") self.assertTrue('00002099' in msg or '00002037' in msg, msg) # we can't create Password Settings Containers under an OU either try: rogue_psc = "CN=Rogue-PSO-container,%s" % self.ou self.ldb.add({ "dn": rogue_psc, "objectclass": "msDS-PasswordSettingsContainer" }) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_NAMING_VIOLATION, msg) self.assertTrue('00002099' in msg or '00002037' in msg, msg) base_dn = self.ldb.get_default_basedn() rogue_psc = "CN=Rogue-PSO-container,CN=Computers,%s" % base_dn self.ldb.add({ "dn": rogue_psc, "objectclass": "msDS-PasswordSettingsContainer" }) rogue_pso = PasswordSettings("rogue-PSO", self.ldb, precedence=1, container=rogue_psc, password_len=20) self.add_obj_cleanup([rogue_pso.dn, rogue_psc]) # apply the PSO to a group and check it has no effect on the user user = self.add_user("testuser") group = self.add_group("Group-1") rogue_pso.apply_to(group) self.set_attribute(group, "member", user.dn) self.assert_PSO_applied(user, self.pwd_defaults) # apply the PSO directly to the user and check it has no effect rogue_pso.apply_to(user.dn) self.assert_PSO_applied(user, self.pwd_defaults)
def test_supplementalCredentials_cleartext_pso(self): """Checks that a PSO's cleartext setting can override the domain's""" # create a user that stores plain-text passwords self.add_user(clear_text=True) # check that clear-text is present in the supplementary-credentials self.assert_cleartext(expect_cleartext=True, password=USER_PASS) # create a PSO overriding the plain-text setting & apply it to the user no_plaintext_pso = PasswordSettings("no-plaintext-PSO", self.ldb, precedence=200, store_plaintext=False) self.addCleanup(self.ldb.delete, no_plaintext_pso.dn) userdn = "cn=" + USER_NAME + ",cn=users," + self.base_dn no_plaintext_pso.apply_to(userdn) # set the password to update the cleartext password stored new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) # this time cleartext shouldn't be in the supplementary creds self.assert_cleartext(expect_cleartext=False) # unapply PSO, update password, and check we get the cleartext again no_plaintext_pso.unapply(userdn) new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) self.assert_cleartext(expect_cleartext=True, password=new_password) # Now update the domain setting and check we no longer get cleartext self.set_store_cleartext(False) new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) self.assert_cleartext(expect_cleartext=False) # create a PSO overriding the domain setting & apply it to the user plaintext_pso = PasswordSettings("plaintext-PSO", self.ldb, precedence=100, store_plaintext=True) self.addCleanup(self.ldb.delete, plaintext_pso.dn) plaintext_pso.apply_to(userdn) new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) self.assert_cleartext(expect_cleartext=True, password=new_password)
def test_pso_min_age(self): """Tests that a PSO's min-age is enforced""" pso = PasswordSettings("min-age-PSO", self.ldb, password_len=10, password_age_min=2, complexity=False) self.add_obj_cleanup([pso.dn]) # create a user and apply the PSO user = self.add_user("testuser") pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == pso.dn) # changing the password immediately should fail, even if password is valid valid_password = "******" self.assert_password_invalid(user, valid_password) # then trying the same password later should succeed time.sleep(pso.password_age_min + 0.5) self.assert_password_valid(user, valid_password)
def test_pso_min_age(self): """Tests that a PSO's min-age is enforced""" pso = PasswordSettings("min-age-PSO", self.ldb, password_len=10, password_age_min=1, complexity=False) self.add_obj_cleanup([pso.dn]) # create a user and apply the PSO user = self.add_user("testuser") pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == pso.dn) # changing the password immediately should fail, even if password is valid valid_password = "******" self.assert_password_invalid(user, valid_password) # then trying the same password later (min-age=1sec) should succeed time.sleep(1.5) self.assert_password_valid(user, valid_password)
def test_pso_none_applied(self): """Tests cases where no Resultant PSO should be returned""" # create a PSO that we will check *doesn't* get returned dummy_pso = PasswordSettings("dummy-PSO", self.ldb, password_len=20) self.add_obj_cleanup([dummy_pso.dn]) # you can apply a PSO to other objects (like OUs), but the resultantPSO # attribute should only be returned for users dummy_pso.apply_to(str(self.ou)) res = self.ldb.search(self.ou, attrs=['msDS-ResultantPSO']) self.assertFalse('msDS-ResultantPSO' in res[0]) # create a dummy user and apply the PSO user = self.add_user("testuser") dummy_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == dummy_pso.dn) try: # now clear the ADS_UF_NORMAL_ACCOUNT flag for the user, which should # mean a resultant PSO is no longer returned (we're essentially turning # the user into a DC here, which is a little overkill but tests # behaviour as per the Windows specification) self.set_attribute(user.dn, "userAccountControl", str(dsdb.UF_WORKSTATION_TRUST_ACCOUNT), operation=FLAG_MOD_REPLACE) except ldb.LdbError as e: (num, msg) = e.args self.fail("Failed to change user into a workstation: {msg}") self.assertIsNone(user.get_resultant_PSO()) try: # reset it back to a normal user account self.set_attribute(user.dn, "userAccountControl", str(dsdb.UF_NORMAL_ACCOUNT), operation=FLAG_MOD_REPLACE) except ldb.LdbError as e: (num, msg) = e.args self.fail("Failed to change user back into a user: {msg}") self.assertTrue(user.get_resultant_PSO() == dummy_pso.dn) # no PSO should be returned if RID is equal to DOMAIN_USER_RID_KRBTGT # (note this currently fails against Windows due to a Windows bug) krbtgt_user = "******" % self.ldb.domain_dn() dummy_pso.apply_to(krbtgt_user) res = self.ldb.search(krbtgt_user, attrs=['msDS-ResultantPSO']) self.assertFalse('msDS-ResultantPSO' in res[0])
def _create_pso(self, pso_name): """Creates a PSO for use in other tests""" # the new PSO will take the current domain settings by default pso_settings = PasswordSettings(None, self.ldb) pso_settings.name = pso_name pso_settings.password_len = 10 pso_settings.precedence = 200 (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "200", "--min-pwd-length=10", "-H", self.server, self.user_auth) # make sure we clean-up after the test completes pso_settings.dn = "CN=%s,%s" %(pso_name, self.pso_container) self.obj_cleanup.append(pso_settings.dn) # sanity-check the cmd was successful self.assertCmdSuccess(result, out, err) self.assertEquals(err,"","Shouldn't be any error messages") self.assertIn("successfully created", out) self.check_pso(pso_name, pso_settings) return pso_settings
def setUp(self): super(PasswordSettingsTestCase, self).setUp() self.host_url = "ldap://%s" % env_get_var_value("SERVER_IP") self.ldb = samba.tests.connect_samdb(self.host_url) # create a temp OU to put this test's users into self.ou = samba.tests.create_test_ou(self.ldb, "password_settings") # update DC to allow password changes for the duration of this test self.allow_password_changes() # store the current password-settings for the domain self.pwd_defaults = PasswordSettings(None, self.ldb) self.test_objs = []
def test_pso_max_age(self): """Tests that a PSO's max-age is used""" # create PSOs that use the domain's max-age +/- 1 day domain_max_age = self.pwd_defaults.password_age_max day_in_secs = 60 * 60 * 24 higher_max_age = domain_max_age + day_in_secs lower_max_age = domain_max_age - day_in_secs longer_pso = PasswordSettings("longer-age-PSO", self.ldb, precedence=5, password_age_max=higher_max_age) shorter_pso = PasswordSettings("shorter-age-PSO", self.ldb, precedence=1, password_age_max=lower_max_age) self.add_obj_cleanup([longer_pso.dn, shorter_pso.dn]) user = self.add_user("testuser") # we can't wait around long enough for the max-age to expire, so # instead just check the msDS-UserPasswordExpiryTimeComputed for # the user attrs = ['msDS-UserPasswordExpiryTimeComputed'] res = self.ldb.search(user.dn, attrs=attrs) domain_expiry = int(res[0]['msDS-UserPasswordExpiryTimeComputed'][0]) # apply the longer PSO and check the expiry-time becomes longer longer_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == longer_pso.dn) res = self.ldb.search(user.dn, attrs=attrs) new_expiry = int(res[0]['msDS-UserPasswordExpiryTimeComputed'][0]) # use timestamp diff of 1 day - 1 minute. The new expiry should still # be greater than this, without getting into nano-second granularity approx_timestamp_diff = (day_in_secs - 60) * (1e7) self.assertTrue(new_expiry > domain_expiry + approx_timestamp_diff) # apply the shorter PSO and check the expiry-time is shorter shorter_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == shorter_pso.dn) res = self.ldb.search(user.dn, attrs=attrs) new_expiry = int(res[0]['msDS-UserPasswordExpiryTimeComputed'][0]) self.assertTrue(new_expiry < domain_expiry - approx_timestamp_diff)
def test_supplementalCredentials_cleartext_pso(self): """Checks that a PSO's cleartext setting can override the domain's""" # create a user that stores plain-text passwords self.add_user(clear_text=True) # check that clear-text is present in the supplementary-credentials self.assert_cleartext(expect_cleartext=True, password=USER_PASS) # create a PSO overriding the plain-text setting & apply it to the user no_plaintext_pso = PasswordSettings("no-plaintext-PSO", self.ldb, precedence=200, store_plaintext=False) self.addCleanup(self.ldb.delete, no_plaintext_pso.dn) userdn = "cn=" + USER_NAME + ",cn=users," + self.base_dn no_plaintext_pso.apply_to(userdn) # set the password to update the cleartext password stored new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) # this time cleartext shouldn't be in the supplementary creds self.assert_cleartext(expect_cleartext=False) # unapply PSO, update password, and check we get the cleartext again no_plaintext_pso.unapply(userdn) new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) self.assert_cleartext(expect_cleartext=True, password=new_password) # Now update the domain setting and check we no longer get cleartext self.set_store_cleartext(False) new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) self.assert_cleartext(expect_cleartext=False) # create a PSO overriding the domain setting & apply it to the user plaintext_pso = PasswordSettings("plaintext-PSO", self.ldb, precedence=100, store_plaintext=True) self.addCleanup(self.ldb.delete, plaintext_pso.dn) plaintext_pso.apply_to(userdn) new_password = samba.generate_random_password(32, 32) self.ldb.setpassword("(sAMAccountName=%s)" % USER_NAME, new_password) self.assert_cleartext(expect_cleartext=True, password=new_password)
def test_pso_invalid_location(self): """Tests that PSOs in an invalid location have no effect""" # PSOs should only be able to be created within a Password Settings # Container object. Trying to create one under an OU should fail try: rogue_pso = PasswordSettings("rogue-PSO", self.ldb, precedence=1, complexity=False, password_len=20, container=self.ou) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_NAMING_VIOLATION, msg) # Windows returns 2099 (Illegal superior), Samba returns 2037 # (Naming violation - "not a valid child class") self.assertTrue('00002099' in msg or '00002037' in msg, msg) # we can't create Password Settings Containers under an OU either try: rogue_psc = "CN=Rogue-PSO-container,%s" % self.ou self.ldb.add({"dn": rogue_psc, "objectclass": "msDS-PasswordSettingsContainer"}) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_NAMING_VIOLATION, msg) self.assertTrue('00002099' in msg or '00002037' in msg, msg) base_dn = self.ldb.get_default_basedn() rogue_psc = "CN=Rogue-PSO-container,CN=Computers,%s" % base_dn self.ldb.add({"dn": rogue_psc, "objectclass": "msDS-PasswordSettingsContainer"}) rogue_pso = PasswordSettings("rogue-PSO", self.ldb, precedence=1, container=rogue_psc, password_len=20) self.add_obj_cleanup([rogue_pso.dn, rogue_psc]) # apply the PSO to a group and check it has no effect on the user user = self.add_user("testuser") group = self.add_group("Group-1") rogue_pso.apply_to(group) self.set_attribute(group, "member", user.dn) self.assert_PSO_applied(user, self.pwd_defaults) # apply the PSO directly to the user and check it has no effect rogue_pso.apply_to(user.dn) self.assert_PSO_applied(user, self.pwd_defaults)
def test_pso_none_applied(self): """Tests cases where no Resultant PSO should be returned""" # create a PSO that we will check *doesn't* get returned dummy_pso = PasswordSettings("dummy-PSO", self.ldb, password_len=20) self.add_obj_cleanup([dummy_pso.dn]) # you can apply a PSO to other objects (like OUs), but the resultantPSO # attribute should only be returned for users dummy_pso.apply_to(str(self.ou)) res = self.ldb.search(self.ou, attrs=['msDS-ResultantPSO']) self.assertFalse('msDS-ResultantPSO' in res[0]) # create a dummy user and apply the PSO user = self.add_user("testuser") dummy_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == dummy_pso.dn) # now clear the ADS_UF_NORMAL_ACCOUNT flag for the user, which should # mean a resultant PSO is no longer returned (we're essentially turning # the user into a DC here, which is a little overkill but tests # behaviour as per the Windows specification) self.set_attribute(user.dn, "userAccountControl", str(dsdb.UF_WORKSTATION_TRUST_ACCOUNT), operation=FLAG_MOD_REPLACE) self.assertTrue(user.get_resultant_PSO() == None) # reset it back to a normal user account self.set_attribute(user.dn, "userAccountControl", str(dsdb.UF_NORMAL_ACCOUNT), operation=FLAG_MOD_REPLACE) self.assertTrue(user.get_resultant_PSO() == dummy_pso.dn) # no PSO should be returned if RID is equal to DOMAIN_USER_RID_KRBTGT # (note this currently fails against Windows due to a Windows bug) krbtgt_user = "******" % self.ldb.domain_dn() dummy_pso.apply_to(krbtgt_user) res = self.ldb.search(krbtgt_user, attrs=['msDS-ResultantPSO']) self.assertFalse('msDS-ResultantPSO' in res[0])
def test_pso_max_age(self): """Tests that a PSO's max-age is used""" # create PSOs that use the domain's max-age +/- 1 day domain_max_age = self.pwd_defaults.password_age_max day_in_secs = 60 * 60 * 24 higher_max_age = domain_max_age + day_in_secs lower_max_age = domain_max_age - day_in_secs longer_pso = PasswordSettings("longer-age-PSO", self.ldb, precedence=5, password_age_max=higher_max_age) shorter_pso = PasswordSettings("shorter-age-PSO", self.ldb, precedence=1, password_age_max=lower_max_age) self.add_obj_cleanup([longer_pso.dn, shorter_pso.dn]) user = self.add_user("testuser") # we can't wait around long enough for the max-age to expire, so instead # just check the msDS-UserPasswordExpiryTimeComputed for the user attrs=['msDS-UserPasswordExpiryTimeComputed'] res = self.ldb.search(user.dn, attrs=attrs) domain_expiry = int(res[0]['msDS-UserPasswordExpiryTimeComputed'][0]) # apply the longer PSO and check the expiry-time becomes longer longer_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == longer_pso.dn) res = self.ldb.search(user.dn, attrs=attrs) new_expiry = int(res[0]['msDS-UserPasswordExpiryTimeComputed'][0]) # use timestamp diff of 1 day - 1 minute. The new expiry should still # be greater than this, without getting into nano-second granularity approx_timestamp_diff = (day_in_secs - 60) * (1e7) self.assertTrue(new_expiry > domain_expiry + approx_timestamp_diff) # apply the shorter PSO and check the expiry-time is shorter shorter_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == shorter_pso.dn) res = self.ldb.search(user.dn, attrs=attrs) new_expiry = int(res[0]['msDS-UserPasswordExpiryTimeComputed'][0]) self.assertTrue(new_expiry < domain_expiry - approx_timestamp_diff)
def _create_pso(self, pso_name): """Creates a PSO for use in other tests""" # the new PSO will take the current domain settings by default pso_settings = PasswordSettings(None, self.ldb) pso_settings.name = pso_name pso_settings.password_len = 10 pso_settings.precedence = 200 (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "200", "--min-pwd-length=10", "-H", self.server, self.user_auth) # make sure we clean-up after the test completes pso_settings.dn = "CN=%s,%s" % (pso_name, self.pso_container) self.obj_cleanup.append(pso_settings.dn) # sanity-check the cmd was successful self.assertCmdSuccess(result, out, err) self.assertEqual(err, "", "Shouldn't be any error messages") self.assertIn("successfully created", out) self.check_pso(pso_name, pso_settings) return pso_settings
def test_pso_permissions(self): """Checks that regular users can't modify/view PSO objects""" user = self.add_user("testuser") # get an ldb connection with the new user's privileges user_ldb = self.get_ldb_connection("testuser", user.get_password(), self.host_url) # regular users should not be able to create a PSO (at least, not in # the default Password Settings container) try: priv_pso = PasswordSettings("priv-PSO", user_ldb, password_len=20) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) # create a PSO as the admin user priv_pso = PasswordSettings("priv-PSO", self.ldb, password_len=20) self.add_obj_cleanup([priv_pso.dn]) # regular users should not be able to apply a PSO to a user try: self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=user_ldb) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) self.assertTrue('00002098' in msg, msg) self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=self.ldb) # regular users should not be able to change a PSO's precedence try: priv_pso.set_precedence(100, samdb=user_ldb) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) self.assertTrue('00002098' in msg, msg) priv_pso.set_precedence(100, samdb=self.ldb) # regular users should not be able to view a PSO's settings pso_attrs = [ "msDS-PSOAppliesTo", "msDS-PasswordSettingsPrecedence", "msDS-PasswordHistoryLength", "msDS-LockoutThreshold", "msDS-PasswordComplexityEnabled" ] # users can see the PSO object's DN, but not its attributes res = user_ldb.search(priv_pso.dn, scope=ldb.SCOPE_BASE, attrs=pso_attrs) self.assertTrue(str(priv_pso.dn) == str(res[0].dn)) for attr in pso_attrs: self.assertFalse(attr in res[0]) # whereas admin users can see everything res = self.ldb.search(priv_pso.dn, scope=ldb.SCOPE_BASE, attrs=pso_attrs) for attr in pso_attrs: self.assertTrue(attr in res[0]) # check replace/delete operations can't be performed by regular users operations = [FLAG_MOD_REPLACE, FLAG_MOD_DELETE] for oper in operations: try: self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=user_ldb, operation=oper) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) self.assertTrue('00002098' in msg, msg) # ...but can be performed by the admin user self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=self.ldb, operation=oper)
def test_pso_basics(self): """Simple tests that a PSO takes effect when applied to a group or user""" # create some PSOs that vary in priority and basic password-len best_pso = PasswordSettings("highest-priority-PSO", self.ldb, precedence=5, password_len=16, history_len=6) medium_pso = PasswordSettings("med-priority-PSO", self.ldb, precedence=15, password_len=10, history_len=4) worst_pso = PasswordSettings("lowest-priority-PSO", self.ldb, precedence=100, complexity=False, password_len=4, history_len=2) # handle PSO clean-up (as they're outside the top-level test OU) self.add_obj_cleanup([worst_pso.dn, medium_pso.dn, best_pso.dn]) # create some groups and apply the PSOs to the groups group1 = self.add_group("Group-1") group2 = self.add_group("Group-2") group3 = self.add_group("Group-3") group4 = self.add_group("Group-4") worst_pso.apply_to(group1) medium_pso.apply_to(group2) best_pso.apply_to(group3) worst_pso.apply_to(group4) # create a user and check the default settings apply to it user = self.add_user("testuser") self.assert_PSO_applied(user, self.pwd_defaults) # add user to a group. Check that the group's PSO applies to the user self.set_attribute(group1, "member", user.dn) self.assert_PSO_applied(user, worst_pso) # add the user to a group with a higher precedence PSO and and check # that now trumps the previous PSO self.set_attribute(group2, "member", user.dn) self.assert_PSO_applied(user, medium_pso) # add the user to the remaining groups. The highest precedence PSO # should now take effect self.set_attribute(group3, "member", user.dn) self.set_attribute(group4, "member", user.dn) self.assert_PSO_applied(user, best_pso) # delete a group membership and check the PSO changes self.set_attribute(group3, "member", user.dn, operation=FLAG_MOD_DELETE) self.assert_PSO_applied(user, medium_pso) # apply the low-precedence PSO directly to the user # (directly applied PSOs should trump higher precedence group PSOs) worst_pso.apply_to(user.dn) self.assert_PSO_applied(user, worst_pso) # remove applying the PSO directly to the user and check PSO changes worst_pso.unapply(user.dn) self.assert_PSO_applied(user, medium_pso) # remove all appliesTo and check we have the default settings again worst_pso.unapply(group1) medium_pso.unapply(group2) worst_pso.unapply(group4) self.assert_PSO_applied(user, self.pwd_defaults)
def test_pso_special_groups(self): """Checks applying a PSO to built-in AD groups takes effect""" # create some PSOs that will apply to special groups default_pso = PasswordSettings("default-PSO", self.ldb, precedence=20, password_len=8, complexity=False) guest_pso = PasswordSettings("guest-PSO", self.ldb, history_len=4, precedence=5, password_len=5) builtin_pso = PasswordSettings("builtin-PSO", self.ldb, history_len=9, precedence=1, password_len=9) admin_pso = PasswordSettings("admin-PSO", self.ldb, history_len=0, precedence=2, password_len=10) self.add_obj_cleanup( [default_pso.dn, guest_pso.dn, admin_pso.dn, builtin_pso.dn]) base_dn = self.ldb.domain_dn() domain_users = "CN=Domain Users,CN=Users,%s" % base_dn domain_guests = "CN=Domain Guests,CN=Users,%s" % base_dn admin_users = "CN=Domain Admins,CN=Users,%s" % base_dn # if we apply a PSO to Domain Users (which all users are a member of) # then that PSO should take effect on a new user default_pso.apply_to(domain_users) user = self.add_user("testuser") self.assert_PSO_applied(user, default_pso) # Apply a PSO to a builtin group. 'Domain Users' should be a member of # Builtin/Users, but builtin groups should be excluded from the PSO # calculation, so this should have no effect builtin_pso.apply_to("CN=Users,CN=Builtin,%s" % base_dn) builtin_pso.apply_to("CN=Administrators,CN=Builtin,%s" % base_dn) self.assert_PSO_applied(user, default_pso) # change the user's primary group to another group (the primaryGroupID # is a little odd in that there's no memberOf backlink for it) self.set_attribute(domain_guests, "member", user.dn) user.set_primary_group(domain_guests) # No PSO is applied to the Domain Guests yet, so the default PSO should # still apply self.assert_PSO_applied(user, default_pso) # now apply a PSO to the guests group, which should trump the default # PSO (because the guest PSO has a better precedence) guest_pso.apply_to(domain_guests) self.assert_PSO_applied(user, guest_pso) # create a new group that's a member of Admin Users nested_group = self.add_group("nested-group") self.set_attribute(admin_users, "member", nested_group) # set the user's primary-group to be the new group self.set_attribute(nested_group, "member", user.dn) user.set_primary_group(nested_group) # we've only changed group membership so far, not the PSO self.assert_PSO_applied(user, guest_pso) # now apply the best-precedence PSO to Admin Users and check it applies # to the user (via the nested-group's membership) admin_pso.apply_to(admin_users) self.assert_PSO_applied(user, admin_pso) # restore the default primaryGroupID so we can safely delete the group user.set_primary_group(domain_users)
def test_pso_special_groups(self): """Checks applying a PSO to built-in AD groups takes effect""" # create some PSOs that will apply to special groups default_pso = PasswordSettings("default-PSO", self.ldb, precedence=20, password_len=8, complexity=False) guest_pso = PasswordSettings("guest-PSO", self.ldb, history_len=4, precedence=5, password_len=5) builtin_pso = PasswordSettings("builtin-PSO", self.ldb, history_len=9, precedence=1, password_len=9) admin_pso = PasswordSettings("admin-PSO", self.ldb, history_len=0, precedence=2, password_len=10) self.add_obj_cleanup([default_pso.dn, guest_pso.dn, admin_pso.dn, builtin_pso.dn]) domain_users = "CN=Domain Users,CN=Users,%s" % self.ldb.domain_dn() domain_guests = "CN=Domain Guests,CN=Users,%s" % self.ldb.domain_dn() admin_users = "CN=Domain Admins,CN=Users,%s" % self.ldb.domain_dn() # if we apply a PSO to Domain Users (which all users are a member of) # then that PSO should take effect on a new user default_pso.apply_to(domain_users) user = self.add_user("testuser") self.assert_PSO_applied(user, default_pso) # Apply a PSO to a builtin group. 'Domain Users' should be a member of # Builtin/Users, but builtin groups should be excluded from the PSO # calculation, so this should have no effect builtin_pso.apply_to("CN=Users,CN=Builtin,%s" % self.ldb.domain_dn()) builtin_pso.apply_to("CN=Administrators,CN=Builtin,%s" % self.ldb.domain_dn()) self.assert_PSO_applied(user, default_pso) # change the user's primary group to another group (the primaryGroupID # is a little odd in that there's no memberOf backlink for it) self.set_attribute(domain_guests, "member", user.dn) user.set_primary_group(domain_guests) # No PSO is applied to the Domain Guests yet, so the default PSO should # still apply self.assert_PSO_applied(user, default_pso) # now apply a PSO to the guests group, which should trump the default # PSO (because the guest PSO has a better precedence) guest_pso.apply_to(domain_guests) self.assert_PSO_applied(user, guest_pso) # create a new group that's a member of Admin Users nested_group = self.add_group("nested-group") self.set_attribute(admin_users, "member", nested_group) # set the user's primary-group to be the new group self.set_attribute(nested_group, "member", user.dn) user.set_primary_group(nested_group) # we've only changed group membership so far, not the PSO self.assert_PSO_applied(user, guest_pso) # now apply the best-precedence PSO to Admin Users and check it applies # to the user (via the nested-group's membership) admin_pso.apply_to(admin_users) self.assert_PSO_applied(user, admin_pso) # restore the default primaryGroupID so we can safely delete the group user.set_primary_group(domain_users)
def test_pso_nested_groups(self): """PSOs operate correctly when applied to nested groups""" # create some PSOs that vary in priority and basic password-len group1_pso = PasswordSettings("group1-PSO", self.ldb, precedence=50, password_len=12, history_len=3) group2_pso = PasswordSettings("group2-PSO", self.ldb, precedence=25, password_len=10, history_len=5, complexity=False) group3_pso = PasswordSettings("group3-PSO", self.ldb, precedence=10, password_len=6, history_len=2) # create some groups and apply the PSOs to the groups group1 = self.add_group("Group-1") group2 = self.add_group("Group-2") group3 = self.add_group("Group-3") group4 = self.add_group("Group-4") group1_pso.apply_to(group1) group2_pso.apply_to(group2) group3_pso.apply_to(group3) # create a PSO and apply it to a group that the user is not a member # of - it should not have any effect on the user unused_pso = PasswordSettings("unused-PSO", self.ldb, precedence=1, password_len=20) unused_pso.apply_to(group4) # handle PSO clean-up (as they're outside the top-level test OU) self.add_obj_cleanup([group1_pso.dn, group2_pso.dn, group3_pso.dn, unused_pso.dn]) # create a user and check the default settings apply to it user = self.add_user("testuser") self.assert_PSO_applied(user, self.pwd_defaults) # add user to a group. Check that the group's PSO applies to the user self.set_attribute(group1, "member", user.dn) self.set_attribute(group2, "member", group1) self.assert_PSO_applied(user, group2_pso) # add another level to the group heirachy & check this PSO takes effect self.set_attribute(group3, "member", group2) self.assert_PSO_applied(user, group3_pso) # invert the PSO precedence and check the new lowest value takes effect group1_pso.set_precedence(3) group2_pso.set_precedence(13) group3_pso.set_precedence(33) self.assert_PSO_applied(user, group1_pso) # delete a PSO and check it no longer applies self.ldb.delete(group1_pso.dn) self.test_objs.remove(group1_pso.dn) self.assert_PSO_applied(user, group2_pso)
def test_pso_equal_precedence(self): """Tests expected PSO wins when several have the same precedence""" # create some PSOs that vary in priority and basic password-len pso1 = PasswordSettings("PSO-1", self.ldb, precedence=5, history_len=1, password_len=11) pso2 = PasswordSettings("PSO-2", self.ldb, precedence=5, history_len=2, password_len=8) pso3 = PasswordSettings("PSO-3", self.ldb, precedence=5, history_len=3, password_len=5, complexity=False) pso4 = PasswordSettings("PSO-4", self.ldb, precedence=5, history_len=4, password_len=13, complexity=False) # handle PSO clean-up (as they're outside the top-level test OU) self.add_obj_cleanup([pso1.dn, pso2.dn, pso3.dn, pso4.dn]) # create some groups and apply the PSOs to the groups group1 = self.add_group("Group-1") group2 = self.add_group("Group-2") group3 = self.add_group("Group-3") group4 = self.add_group("Group-4") pso1.apply_to(group1) pso2.apply_to(group2) pso3.apply_to(group3) pso4.apply_to(group4) # create a user and check the default settings apply to it user = self.add_user("testuser") self.assert_PSO_applied(user, self.pwd_defaults) # add the user to all the groups self.set_attribute(group1, "member", user.dn) self.set_attribute(group2, "member", user.dn) self.set_attribute(group3, "member", user.dn) self.set_attribute(group4, "member", user.dn) # precedence is equal, so the PSO with lowest GUID gets applied pso_list = [pso1, pso2, pso3, pso4] best_pso = self.PSO_with_lowest_GUID(pso_list) self.assert_PSO_applied(user, best_pso) # excluding the winning PSO, apply the other PSOs directly to the user pso_list.remove(best_pso) for pso in pso_list: pso.apply_to(user.dn) # we should now have a different PSO applied (the 2nd lowest GUID) next_best_pso = self.PSO_with_lowest_GUID(pso_list) self.assertTrue(next_best_pso is not best_pso) self.assert_PSO_applied(user, next_best_pso) # bump the precedence of another PSO and it should now win pso_list.remove(next_best_pso) best_pso = pso_list[0] best_pso.set_precedence(4) self.assert_PSO_applied(user, best_pso)
def test_pso_nested_groups(self): """PSOs operate correctly when applied to nested groups""" # create some PSOs that vary in priority and basic password-len group1_pso = PasswordSettings("group1-PSO", self.ldb, precedence=50, password_len=12, history_len=3) group2_pso = PasswordSettings("group2-PSO", self.ldb, precedence=25, password_len=10, history_len=5, complexity=False) group3_pso = PasswordSettings("group3-PSO", self.ldb, precedence=10, password_len=6, history_len=2) # create some groups and apply the PSOs to the groups group1 = self.add_group("Group-1") group2 = self.add_group("Group-2") group3 = self.add_group("Group-3") group4 = self.add_group("Group-4") group1_pso.apply_to(group1) group2_pso.apply_to(group2) group3_pso.apply_to(group3) # create a PSO and apply it to a group that the user is not a member # of - it should not have any effect on the user unused_pso = PasswordSettings("unused-PSO", self.ldb, precedence=1, password_len=20) unused_pso.apply_to(group4) # handle PSO clean-up (as they're outside the top-level test OU) self.add_obj_cleanup( [group1_pso.dn, group2_pso.dn, group3_pso.dn, unused_pso.dn]) # create a user and check the default settings apply to it user = self.add_user("testuser") self.assert_PSO_applied(user, self.pwd_defaults) # add user to a group. Check that the group's PSO applies to the user self.set_attribute(group1, "member", user.dn) self.set_attribute(group2, "member", group1) self.assert_PSO_applied(user, group2_pso) # add another level to the group heirachy & check this PSO takes effect self.set_attribute(group3, "member", group2) self.assert_PSO_applied(user, group3_pso) # invert the PSO precedence and check the new lowest value takes effect group1_pso.set_precedence(3) group2_pso.set_precedence(13) group3_pso.set_precedence(33) self.assert_PSO_applied(user, group1_pso) # delete a PSO and check it no longer applies self.ldb.delete(group1_pso.dn) self.test_objs.remove(group1_pso.dn) self.assert_PSO_applied(user, group2_pso)
def test_pso_add_user(self): """Tests against a 'Domain Users' PSO taking effect on a new user""" # create a PSO that will apply to users by default default_pso = PasswordSettings("default-PSO", self.ldb, precedence=20, password_len=12, complexity=False) self.add_obj_cleanup([default_pso.dn]) # apply the PSO to Domain Users (which all users are a member of). In # theory, this PSO *could* take effect on a new user (but it doesn't) domain_users = "CN=Domain Users,CN=Users,%s" % self.ldb.domain_dn() default_pso.apply_to(domain_users) # first try to add a user with a password that doesn't meet the domain # defaults, to prove that the DC will reject bad passwords during a # user add userdn = "CN=testuser,%s" % self.ou password = self.format_password_for_ldif('abcdef') # Note we use an LDIF operation to ensure that the password gets set # as part of the 'add' operation (whereas self.add_user() adds the user # first, then sets the password later in a 2nd step) try: ldif = """ dn: %s objectClass: user sAMAccountName: testuser unicodePwd:: %s """ % (userdn, password) self.ldb.add_ldif(ldif) self.fail() except ldb.LdbError as e: (num, msg) = e.args # error codes differ between Samba and Windows self.assertTrue( num == ldb.ERR_UNWILLING_TO_PERFORM or num == ldb.ERR_CONSTRAINT_VIOLATION, msg) self.assertTrue('0000052D' in msg, msg) # now use a password that meets the domain defaults, but doesn't meet # the PSO requirements. Note that Windows allows this, i.e. it doesn't # honour the PSO during the add operation password = self.format_password_for_ldif('abcde12#') ldif = """ dn: %s objectClass: user sAMAccountName: testuser unicodePwd:: %s """ % (userdn, password) self.ldb.add_ldif(ldif) # Now do essentially the same thing, but set the password in a 2nd step # which proves that the same password doesn't meet the PSO requirements userdn = "CN=testuser2,%s" % self.ou ldif = """ dn: %s objectClass: user sAMAccountName: testuser2 """ % userdn self.ldb.add_ldif(ldif) # now that the user exists, assert that the PSO is honoured try: ldif = """ dn: %s changetype: modify delete: unicodePwd add: unicodePwd unicodePwd:: %s """ % (userdn, password) self.ldb.modify_ldif(ldif) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEqual(num, ldb.ERR_CONSTRAINT_VIOLATION, msg) self.assertTrue('0000052D' in msg, msg) # check setting a password that meets the PSO settings works password = self.format_password_for_ldif('abcdefghijkl') ldif = """ dn: %s changetype: modify delete: unicodePwd add: unicodePwd unicodePwd:: %s """ % (userdn, password) self.ldb.modify_ldif(ldif)
def test_pso_create(self): """Tests basic PSO creation using the samba-tool""" # we expect the PSO to take the current domain settings by default # (we'll set precedence/complexity, the rest should be the defaults) expected_pso = PasswordSettings(None, self.ldb) expected_pso.complexity = False expected_pso.precedence = 100 # check basic PSO creation works pso_name = "test-create-PSO" (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "100", "--complexity=off", "-H", self.server, self.user_auth) # make sure we clean-up after the test completes self.obj_cleanup.append("CN=%s,%s" %(pso_name, self.pso_container)) self.assertCmdSuccess(result, out, err) self.assertEquals(err,"","Shouldn't be any error messages") self.assertIn("successfully created", out) self.check_pso(pso_name, expected_pso) # check creating a PSO with the same name fails (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "100", "--complexity=off", "-H", self.server, self.user_auth) self.assertCmdFail(result, "Ensure that create for existing PSO fails") self.assertIn("already exists", err) # check we need to specify at least one password policy argument pso_name = "test-create-PSO2" (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "100", "-H", self.server, self.user_auth) self.assertCmdFail(result, "Ensure that create for existing PSO fails") self.assertIn("specify at least one password policy setting", err) # create a PSO with different settings and check they match expected_pso.complexity = True expected_pso.store_plaintext = True expected_pso.precedence = 50 expected_pso.password_len = 12 day_in_secs = 60 * 60 * 24 expected_pso.password_age_min = 11 * day_in_secs expected_pso.password_age_max = 50 * day_in_secs (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "50", "--complexity=on", "--store-plaintext=on", "--min-pwd-length=12", "--min-pwd-age=11", "--max-pwd-age=50", "-H", self.server, self.user_auth) self.obj_cleanup.append("CN=%s,%s" %(pso_name, self.pso_container)) self.assertCmdSuccess(result, out, err) self.assertEquals(err,"","Shouldn't be any error messages") self.assertIn("successfully created", out) self.check_pso(pso_name, expected_pso) # check the PSOs we created are present in the 'list' command (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "list"), "-H", self.server, self.user_auth) self.assertCmdSuccess(result, out, err) self.assertIn("test-create-PSO", out) self.assertIn("test-create-PSO2", out)
def test_pso_equal_precedence(self): """Tests expected PSO wins when several have the same precedence""" # create some PSOs that vary in priority and basic password-len pso1 = PasswordSettings("PSO-1", self.ldb, precedence=5, history_len=1, password_len=11) pso2 = PasswordSettings("PSO-2", self.ldb, precedence=5, history_len=2, password_len=8) pso3 = PasswordSettings("PSO-3", self.ldb, precedence=5, history_len=3, password_len=5, complexity=False) pso4 = PasswordSettings("PSO-4", self.ldb, precedence=5, history_len=4, password_len=13, complexity=False) # handle PSO clean-up (as they're outside the top-level test OU) self.add_obj_cleanup([pso1.dn, pso2.dn, pso3.dn, pso4.dn]) # create some groups and apply the PSOs to the groups group1 = self.add_group("Group-1") group2 = self.add_group("Group-2") group3 = self.add_group("Group-3") group4 = self.add_group("Group-4") pso1.apply_to(group1) pso2.apply_to(group2) pso3.apply_to(group3) pso4.apply_to(group4) # create a user and check the default settings apply to it user = self.add_user("testuser") self.assert_PSO_applied(user, self.pwd_defaults) # add the user to all the groups self.set_attribute(group1, "member", user.dn) self.set_attribute(group2, "member", user.dn) self.set_attribute(group3, "member", user.dn) self.set_attribute(group4, "member", user.dn) # precedence is equal, so the PSO with lowest GUID gets applied pso_list = [pso1, pso2, pso3, pso4] best_pso = self.PSO_with_lowest_GUID(pso_list) self.assert_PSO_applied(user, best_pso) # excluding the winning PSO, apply the other PSOs directly to the user pso_list.remove(best_pso) for pso in pso_list: pso.apply_to(user.dn) # we should now have a different PSO applied (the 2nd lowest GUID) next_best_pso = self.PSO_with_lowest_GUID(pso_list) self.assertTrue(next_best_pso is not best_pso) self.assert_PSO_applied(user, next_best_pso) # bump the precedence of another PSO and it should now win pso_list.remove(next_best_pso) best_pso = pso_list[0] best_pso.set_precedence(4) self.assert_PSO_applied(user, best_pso)
def test_pso_add_user(self): """Tests against a 'Domain Users' PSO taking effect on a new user""" # create a PSO that will apply to users by default default_pso = PasswordSettings("default-PSO", self.ldb, precedence=20, password_len=12, complexity=False) self.add_obj_cleanup([default_pso.dn]) # apply the PSO to Domain Users (which all users are a member of). In # theory, this PSO *could* take effect on a new user (but it doesn't) domain_users = "CN=Domain Users,CN=Users,%s" % self.ldb.domain_dn() default_pso.apply_to(domain_users) # first try to add a user with a password that doesn't meet the domain # defaults, to prove that the DC will reject bad passwords during a # user add userdn = "CN=testuser,%s" % self.ou password = base64.b64encode("\"abcdef\"".encode('utf-16-le')) # Note we use an LDIF operation to ensure that the password gets set # as part of the 'add' operation (whereas self.add_user() adds the user # first, then sets the password later in a 2nd step) try: ldif = """ dn: %s objectClass: user sAMAccountName: testuser unicodePwd:: %s """ % (userdn, password) self.ldb.add_ldif(ldif) self.fail() except ldb.LdbError as e: (num, msg) = e.args # error codes differ between Samba and Windows self.assertTrue(num == ldb.ERR_UNWILLING_TO_PERFORM or num == ldb.ERR_CONSTRAINT_VIOLATION, msg) self.assertTrue('0000052D' in msg, msg) # now use a password that meets the domain defaults, but doesn't meet # the PSO requirements. Note that Windows allows this, i.e. it doesn't # honour the PSO during the add operation password = base64.b64encode("\"abcde12#\"".encode('utf-16-le')) ldif = """ dn: %s objectClass: user sAMAccountName: testuser unicodePwd:: %s """ % (userdn, password) self.ldb.add_ldif(ldif) # Now do essentially the same thing, but set the password in a 2nd step # which proves that the same password doesn't meet the PSO requirements userdn = "CN=testuser2,%s" % self.ou ldif = """ dn: %s objectClass: user sAMAccountName: testuser2 """ % userdn self.ldb.add_ldif(ldif) # now that the user exists, assert that the PSO is honoured try: ldif = """ dn: %s changetype: modify delete: unicodePwd add: unicodePwd unicodePwd:: %s """ % (userdn, password) self.ldb.modify_ldif(ldif) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_CONSTRAINT_VIOLATION, msg) self.assertTrue('0000052D' in msg, msg) # check setting a password that meets the PSO settings works password = base64.b64encode("\"abcdefghijkl\"".encode('utf-16-le')) ldif = """ dn: %s changetype: modify delete: unicodePwd add: unicodePwd unicodePwd:: %s """ % (userdn, password) self.ldb.modify_ldif(ldif)
def test_pso_permissions(self): """Checks that regular users can't modify/view PSO objects""" user = self.add_user("testuser") # get an ldb connection with the new user's privileges user_ldb = self.get_ldb_connection("testuser", user.get_password(), self.host_url) # regular users should not be able to create a PSO (at least, not in # the default Password Settings container) try: priv_pso = PasswordSettings("priv-PSO", user_ldb, password_len=20) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) # create a PSO as the admin user priv_pso = PasswordSettings("priv-PSO", self.ldb, password_len=20) self.add_obj_cleanup([priv_pso.dn]) # regular users should not be able to apply a PSO to a user try: self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=user_ldb) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) self.assertTrue('00002098' in msg, msg) self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=self.ldb) # regular users should not be able to change a PSO's precedence try: priv_pso.set_precedence(100, samdb=user_ldb) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) self.assertTrue('00002098' in msg, msg) priv_pso.set_precedence(100, samdb=self.ldb) # regular users should not be able to view a PSO's settings pso_attrs = ["msDS-PSOAppliesTo", "msDS-PasswordSettingsPrecedence", "msDS-PasswordHistoryLength", "msDS-LockoutThreshold", "msDS-PasswordComplexityEnabled"] # users can see the PSO object's DN, but not its attributes res = user_ldb.search(priv_pso.dn, scope=ldb.SCOPE_BASE, attrs=pso_attrs) self.assertTrue(str(priv_pso.dn) == str(res[0].dn)) for attr in pso_attrs: self.assertFalse(attr in res[0]) # whereas admin users can see everything res = self.ldb.search(priv_pso.dn, scope=ldb.SCOPE_BASE, attrs=pso_attrs) for attr in pso_attrs: self.assertTrue(attr in res[0]) # check replace/delete operations can't be performed by regular users operations = [ FLAG_MOD_REPLACE, FLAG_MOD_DELETE ] for oper in operations: try: self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=user_ldb, operation=oper) self.fail() except ldb.LdbError as e: (num, msg) = e.args self.assertEquals(num, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, msg) self.assertTrue('00002098' in msg, msg) # ...but can be performed by the admin user self.set_attribute(priv_pso.dn, "msDS-PSOAppliesTo", user.dn, samdb=self.ldb, operation=oper)
def test_pso_create(self): """Tests basic PSO creation using the samba-tool""" # we expect the PSO to take the current domain settings by default # (we'll set precedence/complexity, the rest should be the defaults) expected_pso = PasswordSettings(None, self.ldb) expected_pso.complexity = False expected_pso.precedence = 100 # check basic PSO creation works pso_name = "test-create-PSO" (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "100", "--complexity=off", "-H", self.server, self.user_auth) # make sure we clean-up after the test completes self.obj_cleanup.append("CN=%s,%s" % (pso_name, self.pso_container)) self.assertCmdSuccess(result, out, err) self.assertEqual(err, "", "Shouldn't be any error messages") self.assertIn("successfully created", out) self.check_pso(pso_name, expected_pso) # check creating a PSO with the same name fails (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "100", "--complexity=off", "-H", self.server, self.user_auth) self.assertCmdFail(result, "Ensure that create for existing PSO fails") self.assertIn("already exists", err) # check we need to specify at least one password policy argument pso_name = "test-create-PSO2" (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "create"), pso_name, "100", "-H", self.server, self.user_auth) self.assertCmdFail(result, "Ensure that create for existing PSO fails") self.assertIn("specify at least one password policy setting", err) # create a PSO with different settings and check they match expected_pso.complexity = True expected_pso.store_plaintext = True expected_pso.precedence = 50 expected_pso.password_len = 12 day_in_secs = 60 * 60 * 24 expected_pso.password_age_min = 11 * day_in_secs expected_pso.password_age_max = 50 * day_in_secs (result, out, err) = self.runsublevelcmd( "domain", ("passwordsettings", "pso", "create"), pso_name, "50", "--complexity=on", "--store-plaintext=on", "--min-pwd-length=12", "--min-pwd-age=11", "--max-pwd-age=50", "-H", self.server, self.user_auth) self.obj_cleanup.append("CN=%s,%s" % (pso_name, self.pso_container)) self.assertCmdSuccess(result, out, err) self.assertEqual(err, "", "Shouldn't be any error messages") self.assertIn("successfully created", out) self.check_pso(pso_name, expected_pso) # check the PSOs we created are present in the 'list' command (result, out, err) = self.runsublevelcmd("domain", ("passwordsettings", "pso", "list"), "-H", self.server, self.user_auth) self.assertCmdSuccess(result, out, err) self.assertIn("test-create-PSO", out) self.assertIn("test-create-PSO2", out)
def test_pso_basics(self): """Simple tests that a PSO takes effect when applied to a group/user""" # create some PSOs that vary in priority and basic password-len best_pso = PasswordSettings("highest-priority-PSO", self.ldb, precedence=5, password_len=16, history_len=6) medium_pso = PasswordSettings("med-priority-PSO", self.ldb, precedence=15, password_len=10, history_len=4) worst_pso = PasswordSettings("lowest-priority-PSO", self.ldb, precedence=100, complexity=False, password_len=4, history_len=2) # handle PSO clean-up (as they're outside the top-level test OU) self.add_obj_cleanup([worst_pso.dn, medium_pso.dn, best_pso.dn]) # create some groups and apply the PSOs to the groups group1 = self.add_group("Group-1") group2 = self.add_group("Group-2") group3 = self.add_group("Group-3") group4 = self.add_group("Group-4") worst_pso.apply_to(group1) medium_pso.apply_to(group2) best_pso.apply_to(group3) worst_pso.apply_to(group4) # create a user and check the default settings apply to it user = self.add_user("testuser") self.assert_PSO_applied(user, self.pwd_defaults) # add user to a group. Check that the group's PSO applies to the user self.set_attribute(group1, "member", user.dn) self.assert_PSO_applied(user, worst_pso) # add the user to a group with a higher precedence PSO and and check # that now trumps the previous PSO self.set_attribute(group2, "member", user.dn) self.assert_PSO_applied(user, medium_pso) # add the user to the remaining groups. The highest precedence PSO # should now take effect self.set_attribute(group3, "member", user.dn) self.set_attribute(group4, "member", user.dn) self.assert_PSO_applied(user, best_pso) # delete a group membership and check the PSO changes self.set_attribute(group3, "member", user.dn, operation=FLAG_MOD_DELETE) self.assert_PSO_applied(user, medium_pso) # apply the low-precedence PSO directly to the user # (directly applied PSOs should trump higher precedence group PSOs) worst_pso.apply_to(user.dn) self.assert_PSO_applied(user, worst_pso) # remove applying the PSO directly to the user and check PSO changes worst_pso.unapply(user.dn) self.assert_PSO_applied(user, medium_pso) # remove all appliesTo and check we have the default settings again worst_pso.unapply(group1) medium_pso.unapply(group2) worst_pso.unapply(group4) self.assert_PSO_applied(user, self.pwd_defaults)