The branch, master has been updated
via 21a88df032c samba-tool user disable: add new
--remove-supplemental-groups option
via f924724e462 samba-tool user disable: make sure that filter matches
only one user
via b19445a8f00 samba-tool user disable: rename filter variable to
search_filter
via 462d0d667c0 samba-tool user disable: set proper --filter option
description
via dd0892a1be8 samba-tool group removemembers: avoid python backtrace
on error
via 300e14674cf python/samdb: no need to set member_base_dn multiple
times
via c9d8e96d2b1 python/samdb: fix group member removal by SID
via a74bc627794 python/samdb: fix check which checks if user is already
member of group
via 190a635b38d python/samdb: rename filter variable to search_filter
via a4f84ba8970 python/samdb: add missing function parameter description
via bba6bb164e8 python/samdb: fix attribute name in parameter
description
from 2c44022c512 third_party: Update socket_wrapper to version 1.4.4
https://git.samba.org/?p=samba.git;a=shortlog;h=master
- Log -----------------------------------------------------------------
commit 21a88df032c6e481b547fad2c48d14243a514035
Author: Björn Baumbach <[email protected]>
Date: Wed Nov 20 17:54:17 2024 +0100
samba-tool user disable: add new --remove-supplemental-groups option
Removes all supplemental groups from a user, what is commonly
wanted when a user is disabled.
Pair-programmed-with: Stefan Metzmacher <[email protected]>
Signed-off-by: Björn Baumbach <[email protected]>
Signed-off-by: Stefan Metzmacher <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
Autobuild-User(master): Björn Baumbach <[email protected]>
Autobuild-Date(master): Thu Jan 23 19:51:05 UTC 2025 on atb-devel-224
commit f924724e462577d2fb6a5077a5ef7b5254cdec53
Author: Björn Baumbach <[email protected]>
Date: Wed Nov 20 17:24:10 2024 +0100
samba-tool user disable: make sure that filter matches only one user
toggle_userAccountFlags() can only handle one user.
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit b19445a8f00cbb64e81e604b818b905f6ae17011
Author: Björn Baumbach <[email protected]>
Date: Wed Nov 20 17:10:12 2024 +0100
samba-tool user disable: rename filter variable to search_filter
filter() is a Python built-in function to filter iterables.
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit 462d0d667c09012f7c81d1b94f739ef8ebe5fd03
Author: Björn Baumbach <[email protected]>
Date: Wed Nov 20 17:03:24 2024 +0100
samba-tool user disable: set proper --filter option description
Seems to be copied from samba-tool user setpassword command.
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit dd0892a1be8cb8715b59b542b688b28a7fafa150
Author: Björn Baumbach <[email protected]>
Date: Tue Nov 26 17:47:30 2024 +0100
samba-tool group removemembers: avoid python backtrace on error
Pair-programmed-with: Stefan Metzmacher <[email protected]>
Signed-off-by: Björn Baumbach <[email protected]>
Signed-off-by: Stefan Metzmacher <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit 300e14674cfbf051f50686297e067a7b96bf091f
Author: Björn Baumbach <[email protected]>
Date: Fri Nov 22 22:35:29 2024 +0100
python/samdb: no need to set member_base_dn multiple times
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit c9d8e96d2b1f12605585c0c2b3a037f9e45fe64c
Author: Björn Baumbach <[email protected]>
Date: Tue Nov 26 15:46:02 2024 +0100
python/samdb: fix group member removal by SID
Otherwise the removal of groupmembers by SID fails silently, because the
DN does not match the the DN in group member list.
Pair-programmed-with: Stefan Metzmacher <[email protected]>
Signed-off-by: Stefan Metzmacher <[email protected]>
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit a74bc6277944efe417fb474a03728742ba1d89f0
Author: Björn Baumbach <[email protected]>
Date: Mon Nov 25 14:05:40 2024 +0100
python/samdb: fix check which checks if user is already member of group
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit 190a635b38d9f2a20dff1f2942872a08338060bf
Author: Björn Baumbach <[email protected]>
Date: Wed Nov 20 23:28:51 2024 +0100
python/samdb: rename filter variable to search_filter
filter() is a Python built-in function to filter iterables.
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit a4f84ba8970457117040ef69f64ac4c284b12f8c
Author: Björn Baumbach <[email protected]>
Date: Wed Nov 20 21:33:49 2024 +0100
python/samdb: add missing function parameter description
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
commit bba6bb164e8672d7bb7581c7df06435065e61330
Author: Björn Baumbach <[email protected]>
Date: Wed Sep 18 19:22:29 2024 +0200
python/samdb: fix attribute name in parameter description
Signed-off-by: Björn Baumbach <[email protected]>
Reviewed-by: Jule Anger <[email protected]>
-----------------------------------------------------------------------
Summary of changes:
docs-xml/manpages/samba-tool.8.xml | 8 ++
python/samba/netcmd/group.py | 2 +-
python/samba/netcmd/user/disable.py | 64 ++++++++++++++--
python/samba/samdb.py | 136 ++++++++++++++++++++++++++++------
python/samba/tests/samba_tool/user.py | 78 +++++++++++++++++++
5 files changed, 259 insertions(+), 29 deletions(-)
Changeset truncated at 500 lines:
diff --git a/docs-xml/manpages/samba-tool.8.xml
b/docs-xml/manpages/samba-tool.8.xml
index 62ce4e690d4..3bd7b75a73e 100644
--- a/docs-xml/manpages/samba-tool.8.xml
+++ b/docs-xml/manpages/samba-tool.8.xml
@@ -2816,6 +2816,14 @@
<refsect3>
<title>user disable <replaceable>username</replaceable></title>
<para>Disable a user account.</para>
+ <variablelist>
+ <varlistentry>
+ <term>--remove-supplemental-groups</term>
+ <listitem><para>
+ Remove user from all groups, but keep the primary group.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
</refsect3>
<refsect3>
diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py
index a7055602253..ea17cebe1e5 100644
--- a/python/samba/netcmd/group.py
+++ b/python/samba/netcmd/group.py
@@ -584,7 +584,7 @@ Example2 shows how to remove a single user account, User2,
from the supergroup A
member_base_dn=member_base_dn)
except Exception as e:
# FIXME: Catch more specific exception
- raise CommandError('Failed to remove members %r from group "%s"' %
(listofmembers, groupname), e)
+ raise CommandError('Failed to remove members %r from group "%s" -
%s' % (listofmembers, groupname, e))
self.outf.write("Removed members from group %s\n" % groupname)
diff --git a/python/samba/netcmd/user/disable.py
b/python/samba/netcmd/user/disable.py
index 5042eead3a5..d959f6d45f8 100644
--- a/python/samba/netcmd/user/disable.py
+++ b/python/samba/netcmd/user/disable.py
@@ -24,6 +24,8 @@ from samba import ldb
from samba.auth import system_session
from samba.netcmd import Command, CommandError, Option
from samba.samdb import SamDB
+from samba.dcerpc import security
+from samba.ndr import ndr_unpack
class cmd_user_disable(Command):
@@ -34,7 +36,13 @@ class cmd_user_disable(Command):
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),
+ Option("--filter",
+ help="LDAP filter to select user",
+ type=str,
+ dest="search_filter"),
+ Option("--remove-supplemental-groups",
+ help="Remove user's supplemental groups",
+ action="store_true"),
]
takes_args = ["username?"]
@@ -46,19 +54,61 @@ class cmd_user_disable(Command):
}
def run(self, username=None, sambaopts=None, credopts=None,
- versionopts=None, filter=None, H=None):
- if username is None and filter is None:
+ versionopts=None, search_filter=None, H=None,
+ remove_supplemental_groups=False):
+ if username is None and search_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))
+ if search_filter is None:
+ search_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)
+
+ samdb.transaction_start()
+ try:
+ res = samdb.search(base=samdb.domain_dn(),
+ expression=search_filter,
+ scope=ldb.SCOPE_SUBTREE,
+ controls=["extended_dn:1:1"],
+ attrs=["objectSid", "memberOf"])
+ user_groups = res[0].get("memberOf")
+ if user_groups is None:
+ user_groups = []
+ user_binary_sid = res[0].get("objectSid", idx=0)
+ user_sid = ndr_unpack(security.dom_sid, user_binary_sid)
+ except IndexError:
+ samdb.transaction_cancel()
+ raise CommandError("Unable to find user '%s'" % (
+ username or search_filter))
+ except Exception as msg:
+ samdb.transaction_cancel()
+ raise CommandError("Failed to find user '%s': '%s'" % (
+ username or search_filter, msg))
+ if len(res) > 1:
+ samdb.transaction_cancel()
+ raise CommandError("Found more than one user '%s'" % (
+ username or search_filter))
+
+ if remove_supplemental_groups:
+ for user_group in user_groups:
+ try:
+ samdb.add_remove_group_members(str(user_group),
+ [str(user_sid)],
+ add_members_operation=False)
+ except Exception as msg:
+ samdb.transaction_cancel()
+ raise CommandError("Failed to remove user from group "
+ "'%s': %s" % (user_group, msg))
+
try:
- samdb.disable_account(filter)
+ samdb.disable_account(search_filter)
except Exception as msg:
- raise CommandError("Failed to disable user '%s': %s" % (username
or filter, msg))
+ samdb.transaction_cancel()
+ raise CommandError("Failed to disable user '%s': %s" % (
+ username or search_filter, msg))
+ samdb.transaction_commit()
diff --git a/python/samba/samdb.py b/python/samba/samdb.py
index eced40a6541..0545aed98eb 100644
--- a/python/samba/samdb.py
+++ b/python/samba/samdb.py
@@ -35,6 +35,7 @@ from samba.common import normalise_int32
from samba.common import get_bytes, cmp
from samba.dcerpc import security
from samba import is_ad_dc_built
+from samba import NTSTATUSError, ntstatus
import binascii
__docformat__ = "restructuredText"
@@ -132,7 +133,7 @@ class SamDB(samba.Ldb):
"""Disables an account
:param search_filter: LDAP filter to find the user (eg
- samccountname=name)
+ sAMAccountName=name)
"""
flags = samba.dsdb.UF_ACCOUNTDISABLE
@@ -142,7 +143,7 @@ class SamDB(samba.Ldb):
"""Enables an account
:param search_filter: LDAP filter to find the user (eg
- samccountname=name)
+ sAMAccountName=name)
"""
flags = samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_PASSWD_NOTREQD
@@ -153,7 +154,7 @@ class SamDB(samba.Ldb):
"""Toggle_userAccountFlags
:param search_filter: LDAP filter to find the user (eg
- samccountname=name)
+ sAMAccountName=name)
:param flags: samba.dsdb.UF_* flags
:param on: on=True (default) => set, on=False => unset
:param strict: strict=False (default) ignore if no action is needed
@@ -197,7 +198,7 @@ userAccountControl: %u
"""Forces a password change at next login
:param search_filter: LDAP filter to find the user (eg
- samccountname=name)
+ sAMAccountName=name)
"""
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
expression=search_filter, attrs=[])
@@ -365,43 +366,116 @@ lockoutTime: 0
return filter
- def add_remove_group_members(self, groupname, members,
+ def add_remove_group_members(self, group, members,
add_members_operation=True,
member_types=None,
member_base_dn=None):
"""Adds or removes group members
- :param groupname: Name of the target group
+ :param group: sAMAccountName, DN, SID or GUID of the target group
:param members: list of group members
:param add_members_operation: Defines if its an add or remove
operation
+ :param member_types: List of object types, used to filter the search
+ for the specified members
+ :param member_base_dn: Base dn for member search
"""
if member_types is None:
member_types = ['user', 'group', 'computer']
- groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
- ldb.binary_encode(groupname),
"CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
+ if member_base_dn is None:
+ member_base_dn = self.domain_dn()
+
+ partial_groupfilter = None
+
+ group_sid = None
+ try:
+ group_sid = security.dom_sid(group)
+ except ValueError:
+ pass
+ if group_sid is not None:
+ partial_groupfilter = "(objectClass=*)"
+
+ group_guid = None
+ if partial_groupfilter is None:
+ try:
+ group_guid = misc.GUID(group)
+ except NTSTATUSError as e:
+ (status, _) = e.args
+ if status != ntstatus.NT_STATUS_INVALID_PARAMETER:
+ raise e
+ if group_guid is not None:
+ partial_groupfilter = "(objectClass=*)"
+
+ if partial_groupfilter is None:
+ group_dn = None
+ try:
+ if isinstance(group, ldb.Dn):
+ group_dn = ldb.Dn(self, group.extended_str(1))
+ else:
+ group_dn = ldb.Dn(self, str(group))
+ except ValueError:
+ pass
+ if group_dn is not None:
+ group_b_sid = group_dn.get_extended_component("SID")
+ group_b_guid = group_dn.get_extended_component("GUID")
+ if group_b_sid is not None:
+ group_sid = ndr_unpack(security.dom_sid, group_b_sid)
+ partial_groupfilter = "(objectClass=*)"
+ elif group_b_guid is not None:
+ group_guid = ndr_unpack(misc.GUID, group_b_guid)
+ partial_groupfilter = "(objectClass=*)"
+ else:
+ search_base = str(group_dn)
+ search_scope = ldb.SCOPE_BASE
+
+ if group_sid is not None:
+ search_base = '<SID=%s>' % group_sid
+ search_scope = ldb.SCOPE_BASE
+
+ if group_guid is not None:
+ search_base = '<GUID=%s>' % group_guid
+ search_scope = ldb.SCOPE_BASE
+
+ if partial_groupfilter is None:
+ search_base = self.domain_dn()
+ search_scope = ldb.SCOPE_SUBTREE
+ partial_groupfilter = "(sAMAccountName=%s)" % (
+ ldb.binary_encode(group))
+
+ groupfilter = "(&%s(objectCategory=%s,%s))" % (
+ partial_groupfilter,
+ "CN=Group,CN=Schema,CN=Configuration",
+ self.domain_dn())
self.transaction_start()
try:
- targetgroup = self.search(base=self.domain_dn(),
scope=ldb.SCOPE_SUBTREE,
- expression=groupfilter, attrs=['member'])
+ targetgroup = self.search(base=search_base,
+ scope=search_scope,
+ expression=groupfilter,
+ controls=["extended_dn:1:1"],
+ attrs=['member'])
if len(targetgroup) == 0:
- raise Exception('Unable to find group "%s"' % groupname)
+ raise Exception('Unable to find group "%s"' % group)
assert(len(targetgroup) == 1)
modified = False
+ if group_sid is not None:
+ targetgroup_dn = '<SID=%s>' % group_sid
+ elif group_guid is not None:
+ targetgroup_dn = '<GUID=%s>' % group_guid
+ else:
+ targetgroup_dn = str(targetgroup[0].dn)
+
addtargettogroup = """
dn: %s
changetype: modify
-""" % (str(targetgroup[0].dn))
+""" % (targetgroup_dn)
for member in members:
targetmember_dn = None
- if member_base_dn is None:
- member_base_dn = self.domain_dn()
-
+ membersid = None
try:
membersid = security.dom_sid(member)
targetmember_dn = "<SID=%s>" % str(membersid)
@@ -420,10 +494,10 @@ changetype: modify
pass
if targetmember_dn is None:
- filter = self.group_member_filter(member, member_types)
+ search_filter = self.group_member_filter(member,
member_types)
targetmember = self.search(base=member_base_dn,
scope=ldb.SCOPE_SUBTREE,
- expression=filter,
+ expression=search_filter,
attrs=[])
if len(targetmember) > 1:
@@ -436,13 +510,33 @@ changetype: modify
raise Exception('Unable to find "%s". Operation
cancelled.' % member)
targetmember_dn = targetmember[0].dn.extended_str(1)
- if add_members_operation is True and
(targetgroup[0].get('member') is None or get_bytes(targetmember_dn) not in
[str(x) for x in targetgroup[0]['member']]):
+ def _is_member(samdb, group, member_dn, member_sid):
+ if group.get('member') is None:
+ return False
+
+ for m in group.get('member'):
+ m_ext_dn = ldb.Dn(samdb, str(m))
+ m_binary_sid = m_ext_dn.get_extended_component("SID")
+ if m_binary_sid:
+ m_sid = ndr_unpack(security.dom_sid, m_binary_sid)
+ if member_sid == m_sid:
+ return True
+ if member_dn == str(m_ext_dn):
+ return True
+
+ return False
+
+ is_member = _is_member(self,
+ targetgroup[0],
+ targetmember_dn,
+ membersid)
+ if add_members_operation is True and not is_member:
modified = True
addtargettogroup += """add: member
member: %s
""" % (str(targetmember_dn))
- elif add_members_operation is False and
(targetgroup[0].get('member') is not None and get_bytes(targetmember_dn) in
targetgroup[0]['member']):
+ elif add_members_operation is False and is_member:
modified = True
addtargettogroup += """delete: member
member: %s
@@ -875,7 +969,7 @@ member: %s
"""Sets the password for a user
:param search_filter: LDAP filter to find the user (eg
- samccountname=name)
+ sAMAccountName=name)
:param password: Password for the user
:param force_change_at_next_login: Force password change
"""
@@ -918,7 +1012,7 @@ unicodePwd:: %s
"""Sets the account expiry for a user
:param search_filter: LDAP filter to find the user (eg
- samaccountname=name)
+ sAMAccountName=name)
:param expiry_seconds: expiry time from now in seconds
:param no_expiry_req: if set, then don't expire password
"""
diff --git a/python/samba/tests/samba_tool/user.py
b/python/samba/tests/samba_tool/user.py
index 290d5daebe1..caef93407bf 100644
--- a/python/samba/tests/samba_tool/user.py
+++ b/python/samba/tests/samba_tool/user.py
@@ -1126,6 +1126,41 @@ sAMAccountName: %s
self.assertCmdSuccess(result, out, err, "Error running user
unlock")
self.assertEqual(err, "", "Shouldn't be any error messages")
+ def test_disable_remove_supplemental_groups(self):
+ """disable user and remove supplemental groups"""
+ username = "userRemoveGroups"
+ user = self._randomUser({"name": username})
+ self._create_user(user)
+
+ usergroups = self._get_groups(username)
+ self.assertTrue(len(usergroups) == 1, "exactly one membership
expected")
+ self.assertEqual(usergroups[0],
+ "Domain Users",
+ "Unexpected groupmembership")
+
+ self._add_groupmember("Domain Admins", username)
+ self._add_groupmember("Print Operators", username)
+
+ usergroups = self._get_groups(username)
+ self.assertTrue(len(usergroups) == 3, "exactly 3 memberships expected")
+
+ (result, out, err) = self.runsubcmd(
+ "user", "disable", username,
+ "--remove-supplemental-groups",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(
+ result, out, err,
+ "Error running user disable --remove-supplemental-groups")
+ self.assertEqual(err, "",
+ "Shouldn't be any error messages from user disable")
+
+ usergroups = self._get_groups(username)
+ self.assertTrue(len(usergroups) == 1, "exactly one membership
expected")
+ self.assertEqual(usergroups[0], "Domain Users",
+ "Unexpected groupmembership")
+
def _randomUser(self, base=None):
"""create a user with random attribute values, you can specify base
attributes"""
if base is None:
@@ -1271,3 +1306,46 @@ template """
return userlist[0]
else:
return None
+
+ def _add_groupmember(self, group, user):
+ (result, out, err) = self.runsubcmd(
+ "group", "addmembers", group, user,
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(
+ result, out, err, "Error running group addmembers")
+ self.assertEqual(
+ err,
+ "",
+ "Shouldn't be any error messages from group addmembers")
+
+ return out.rstrip().split("\n")
+
+ def _remove_groupmember(self, group, user):
+ (result, out, err) = self.runsubcmd(
+ "group", "removemembers", group, user,
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(
+ result, out, err, "Error running group removemembers")
+ self.assertEqual(
+ err,
+ "",
+ "Shouldn't be any error messages from group removemembers")
+
+ return out.rstrip().split("\n")
+
+ def _get_groups(self, user):
+ (result, out, err) = self.runsubcmd(
+ "user", "getgroups", user,
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(result, out, err, "Error running user getgroups")
+ self.assertEqual(err,
+ "",
+ "Shouldn't be any error messages from user getgroups")
+
+ return out.rstrip().split("\n")
--
Samba Shared Repository