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_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_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_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_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_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_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_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.assertIsNone(user.get_resultant_PSO()) # 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_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_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_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_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_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)
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_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)