[SCM] Samba Shared Repository - branch master updated

Douglas Bagnall dbagnall at samba.org
Wed Nov 4 00:20:03 UTC 2020


The branch, master has been updated
       via  a1b021200e3 selftest: add test for new "samba-tool user unlock" command
       via  0bc93500a8b samba-tool: add new "user unlock" command
      from  27480333fdc s3:vfs: Document the encryption_required flag in vfs.h

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit a1b021200e3d068631798942a1f219b26afadca7
Author: Björn Baumbach <bb at sernet.de>
Date:   Thu Oct 29 12:38:51 2020 +0100

    selftest: add test for new "samba-tool user unlock" command
    
    Signed-off-by: Björn Baumbach <bb at sernet.de>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    
    Autobuild-User(master): Douglas Bagnall <dbagnall at samba.org>
    Autobuild-Date(master): Wed Nov  4 00:19:25 UTC 2020 on sn-devel-184

commit 0bc93500a8bfb268085b02379375741903961f4e
Author: Björn Baumbach <bb at sernet.de>
Date:   Thu Oct 22 17:29:56 2020 +0200

    samba-tool: add new "user unlock" command
    
    Can be used to unlock a user when the badPwdCount has been reached.
    
    Introduces SamDB error classes, as suggested by
    Douglas Bagnall <douglas.bagnall at catalyst.net.nz> - thanks!
    This helps to handle expected failures.
    Tracebacks of really unexpected failures will not be hidden.
    
    Signed-off-by: Björn Baumbach <bb at sernet.de>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>

-----------------------------------------------------------------------

Summary of changes:
 docs-xml/manpages/samba-tool.8.xml                 |  6 ++
 python/samba/netcmd/user.py                        | 74 +++++++++++++++++++++-
 python/samba/samdb.py                              | 30 +++++++++
 python/samba/tests/samba_tool/user.py              | 41 ++++++++++++
 source4/dsdb/tests/python/password_lockout.py      | 20 ++++++
 source4/dsdb/tests/python/password_lockout_base.py |  1 +
 6 files changed, 171 insertions(+), 1 deletion(-)


Changeset truncated at 500 lines:

diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml
index ccaaa8b432a..a7e8a7c9d1a 100644
--- a/docs-xml/manpages/samba-tool.8.xml
+++ b/docs-xml/manpages/samba-tool.8.xml
@@ -1465,6 +1465,12 @@
 	<para>Sets or resets the password of a user account.</para>
 </refsect3>
 
+<refsect3>
+	<title>user unlock <replaceable>username</replaceable> [options]</title>
+	<para>This command unlocks a user account in the Active Directory
+	domain.</para>
+</refsect3>
+
 <refsect3>
 	<title>user getpassword <replaceable>username</replaceable> [options]</title>
 	<para>Gets the password of a user account.</para>
diff --git a/python/samba/netcmd/user.py b/python/samba/netcmd/user.py
index f9762e761ea..b483dcf5591 100644
--- a/python/samba/netcmd/user.py
+++ b/python/samba/netcmd/user.py
@@ -33,7 +33,7 @@ import binascii
 from subprocess import Popen, PIPE, STDOUT, check_call, CalledProcessError
 from getpass import getpass
 from samba.auth import system_session
-from samba.samdb import SamDB
+from samba.samdb import SamDB, SamDBError, SamDBNotFoundError
 from samba.dcerpc import misc
 from samba.dcerpc import security
 from samba.dcerpc import drsblobs
@@ -3257,6 +3257,77 @@ unixHomeDirectory: {6}
             self.outf.write("Modified User '{}' successfully\n"
                             .format(username))
 
+class cmd_user_unlock(Command):
+    """Unlock a user account.
+
+    This command unlocks a user account in the Active Directory domain. The
+    username specified on the command is the sAMAccountName. The username may
+    also be specified using the --filter option.
+
+    The command may be run from the root userid or another authorized userid.
+    The -H or --URL= option can be used to execute the command against a remote
+    server.
+
+    Example:
+    samba-tool user unlock user1 -H ldap://samba.samdom.example.com \\
+        --username=Administrator --password=Passw0rd
+
+    The example shows how to unlock a user account in the domain against a
+    remote LDAP server. The -H parameter is used to specify the remote target
+    server. The --username= and --password= options are used to pass the
+    username and password of a user that exists on the remote server and is
+    authorized to issue the command on that server.
+"""
+
+    synopsis = "%prog (<username>|--filter <filter>) [options]"
+
+    takes_options = [
+        Option("-H",
+               "--URL",
+               help="LDB URL for database or target server",
+               type=str,
+               metavar="URL",
+               dest="H"),
+        Option("--filter",
+               help="LDAP Filter to set password on",
+               type=str),
+    ]
+
+    takes_args = ["username?"]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+    }
+
+    def run(self,
+            username=None,
+            sambaopts=None,
+            credopts=None,
+            versionopts=None,
+            filter=None,
+            H=None):
+        if username is None and filter is None:
+            raise CommandError("Either the username or '--filter' must be "
+                               "specified!")
+
+        if filter is None:
+            filter = ("(&(objectClass=user)(sAMAccountName=%s))" % (
+                ldb.binary_encode(username)))
+
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H,
+                      session_info=system_session(),
+                      credentials=creds,
+                      lp=lp)
+        try:
+            samdb.unlock_account(filter)
+        except (SamDBError, ldb.LdbError) as msg:
+            raise CommandError("Failed to unlock user '%s': %s" % (
+                               username or filter, msg))
 
 class cmd_user_sensitive(Command):
     """Set/unset or show UF_NOT_DELEGATED for an account."""
@@ -3336,5 +3407,6 @@ class cmd_user(SuperCommand):
     subcommands["show"] = cmd_user_show()
     subcommands["move"] = cmd_user_move()
     subcommands["rename"] = cmd_user_rename()
+    subcommands["unlock"] = cmd_user_unlock()
     subcommands["addunixattrs"] = cmd_user_add_unix_attrs()
     subcommands["sensitive"] = cmd_user_sensitive()
diff --git a/python/samba/samdb.py b/python/samba/samdb.py
index 0ec91ed3970..e8aee496352 100644
--- a/python/samba/samdb.py
+++ b/python/samba/samdb.py
@@ -42,6 +42,11 @@ __docformat__ = "restructuredText"
 def get_default_backend_store():
     return "tdb"
 
+class SamDBError(Exception):
+    pass
+
+class SamDBNotFoundError(SamDBError):
+    pass
 
 class SamDB(samba.Ldb):
     """The SAM database."""
@@ -179,6 +184,31 @@ dn: %s
 changetype: modify
 replace: pwdLastSet
 pwdLastSet: 0
+""" % (user_dn)
+        self.modify_ldif(mod)
+
+    def unlock_account(self, search_filter):
+        """Unlock a user account by resetting lockoutTime to 0.
+        This does also reset the badPwdCount to 0.
+
+        :param search_filter: LDAP filter to find the user (e.g.
+            sAMAccountName=username)
+        """
+        res = self.search(base=self.domain_dn(),
+                          scope=ldb.SCOPE_SUBTREE,
+                          expression=search_filter,
+                          attrs=[])
+        if len(res) == 0:
+            raise SamDBNotFoundError('Unable to find user "%s"' % search_filter)
+        if len(res) != 1:
+            raise SamDBError('User "%s" is not unique' % search_filter)
+        user_dn = res[0].dn
+
+        mod = """
+dn: %s
+changetype: modify
+replace: lockoutTime
+lockoutTime: 0
 """ % (user_dn)
         self.modify_ldif(mod)
 
diff --git a/python/samba/tests/samba_tool/user.py b/python/samba/tests/samba_tool/user.py
index 22f76333ae2..07eb09b24d5 100644
--- a/python/samba/tests/samba_tool/user.py
+++ b/python/samba/tests/samba_tool/user.py
@@ -800,6 +800,47 @@ sAMAccountName: %s
         self._check_posix_user(user)
         self.runsubcmd("user", "delete", user["name"])
 
+    # Test: samba-tool user unlock
+    # This test does not verify that the command unlocks the user, it just
+    # tests the command itself. The unlock test, which unlocks locked users,
+    # is located in the 'samba4.ldap.password_lockout' test in
+    # source4/dsdb/tests/python/password_lockout.py
+    def test_unlock(self):
+
+        # try to unlock a nonexistent user, this should fail
+        nonexistentusername = "userdoesnotexist"
+        (result, out, err) = self.runsubcmd(
+            "user", "unlock", nonexistentusername)
+        self.assertCmdFail(result, "Ensure that unlock nonexistent user fails")
+        self.assertIn("Failed to unlock user '%s'" % nonexistentusername, err)
+        self.assertIn("Unable to find user", err)
+
+        # try to unlock with insufficient permissions, this should fail
+        unprivileged_username = "unprivilegedunlockuser"
+        unlocktest_username = "usertounlock"
+
+        self.runsubcmd("user", "add", unprivileged_username, "Passw0rd")
+        self.runsubcmd("user", "add", unlocktest_username, "Passw0rd")
+
+        (result, out, err) = self.runsubcmd(
+            "user", "unlock", unlocktest_username,
+            "-H", "ldap://%s" % os.environ["DC_SERVER"],
+            "-U%s%%%s" % (unprivileged_username,
+                          "Passw0rd"))
+        self.assertCmdFail(result, "Fail with LDAP_INSUFFICIENT_ACCESS_RIGHTS")
+        self.assertIn("Failed to unlock user '%s'" % unlocktest_username, err)
+        self.assertIn("LDAP error 50 LDAP_INSUFFICIENT_ACCESS_RIGHTS", err)
+
+        self.runsubcmd("user", "delete", unprivileged_username)
+        self.runsubcmd("user", "delete", unlocktest_username)
+
+        # run unlock against test users
+        for user in self.users:
+            (result, out, err) = self.runsubcmd(
+                "user", "unlock", user["name"])
+            self.assertCmdSuccess(result, out, err, "Error running user unlock")
+            self.assertEqual(err, "", "Shouldn't be any error messages")
+
     def _randomUser(self, base={}):
         """create a user with random attribute values, you can specify base attributes"""
         user = {
diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py
index cbe15c33742..445944862b8 100755
--- a/source4/dsdb/tests/python/password_lockout.py
+++ b/source4/dsdb/tests/python/password_lockout.py
@@ -17,6 +17,7 @@ sys.path.insert(0, "bin/python")
 import samba
 
 from samba.tests.subunitrun import TestProgram, SubunitOptions
+from samba.netcmd.main import cmd_sambatool
 
 import samba.getopt as options
 
@@ -133,6 +134,17 @@ replace: lockoutTime
 lockoutTime: 0
 """)
 
+    def _reset_samba_tool(self, res):
+        username = res[0]["sAMAccountName"][0]
+
+        cmd = cmd_sambatool.subcommands['user'].subcommands['unlock']
+        result = cmd._run("samba-tool user unlock",
+                          username,
+                          "-H%s" % host_url,
+                          "-U%s%%%s" % (global_creds.get_username(),
+                                        global_creds.get_password()))
+        self.assertEqual(result, None)
+
     def _reset_ldap_userAccountControl(self, res):
         self.assertTrue("userAccountControl" in res[0])
         self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
@@ -157,6 +169,8 @@ userAccountControl: %d
             self._reset_ldap_lockoutTime(res)
         elif method == "samr":
             self._reset_samr(res)
+        elif method == "samba-tool":
+            self._reset_samba_tool(res)
         else:
             self.assertTrue(False, msg="Invalid reset method[%s]" % method)
 
@@ -635,6 +649,12 @@ userPassword: thatsAcomplPASS2XYZ
                                                           "samr",
                                                           initial_lastlogon_relation='greater')
 
+    # just test "samba-tool user unlock" command once
+    def test_userPassword_lockout_with_clear_change_krb5_ldap_samba_tool(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
+                                                          self.lockout2krb5_ldb,
+                                                          "samba-tool")
+
     def test_multiple_logon_krb5(self):
         self._test_multiple_logon(self.lockout1krb5_creds)
 
diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py
index 17ae807faf6..0f9617da1e6 100644
--- a/source4/dsdb/tests/python/password_lockout_base.py
+++ b/source4/dsdb/tests/python/password_lockout_base.py
@@ -113,6 +113,7 @@ class BasePasswordTestCase(PasswordTestCase):
             print("\033[01;32m %s \033[00m\n" % msg)
         attrs = [
             "objectSid",
+           "sAMAccountName",
            "badPwdCount",
            "badPasswordTime",
            "lastLogon",


-- 
Samba Shared Repository



More information about the samba-cvs mailing list