On 02/13/2013 02:14 PM, Alexander Bokovoy wrote:
> On Wed, 13 Feb 2013, Martin Kosek wrote:
>> On 02/01/2013 01:35 PM, Martin Kosek wrote:
>>> On 01/24/2013 03:04 PM, Simo Sorce wrote:
>>>> On Thu, 2013-01-24 at 08:15 +0100, Martin Kosek wrote:
>>>>> On 01/23/2013 02:23 PM, Simo Sorce wrote:
>>>>>> On Wed, 2013-01-23 at 09:10 +0100, Martin Kosek wrote:
>>>>>>> On 01/19/2013 07:35 PM, Simo Sorce wrote:
>>>>>>>> On Fri, 2013-01-18 at 18:24 +0100, Martin Kosek wrote:
>>>>>>>>> How this works:
>>>>>>>>>    1. When a trusted domain user is tested, AD GC is searched
>>>>>>>>>       for the user entry Distinguished Name
>>>>>>>>
>>>>>>>> My head is not clear today but it looks to me you are doing 2 searches.
>>>>>>>> One to go from samAccountName -> DNa dn then a second for DN -> SID.
>>>>>>>>
>>>>>>>> Why are you doing 2 searches ? The first one can return you the
>>>>>>>> ObjectSid already.
>>>>>>>>
>>>>>>>> Simo.
>>>>>>>
>>>>>>> I had to do 2 searches because GC refuses to give me tokenGroups 
>>>>>>> attribute
>>>>>>> content when I do not search with exact DN and LDAP SCOPE_BASE. So I
>>>>>>> have to do
>>>>>>> the first search to find out the DN of the searched user and then a 
>>>>>>> second
>>>>>>> query to get the tokenGroups (and ObjectSid).
>>>>>>
>>>>>> I see, yes that makes sense, would you mind adding a comment to this
>>>>>> effect so we do not try to 'optimize' at some point ?
>>>>>> I have no additional concerns then.
>>>>>>
>>>>>> Simo.
>>>>>>
>>>>>
>>>>> Hello Simo,
>>>>>
>>>>> Thanks for review. Anyway, there is already a relevant comment in 
>>>>> dcerpc.py,
>>>>> where the double search is performed:
>>>>>
>>>>> ...
>>>>>     def get_trusted_domain_user_and_groups(self, object_name):
>>>>> ...
>>>>>         entries = 
>>>>> self.get_trusted_domain_objects(components.get('domain'),
>>>>>                 components.get('flatname'), filter, attrs,
>>>>> _ldap.SCOPE_SUBTREE)
>>>>>
>>>>>         # Get SIDs of user object and it's groups
>>>>>         # tokenGroups attribute must be read with scope BASE to avoid
>>>>> search error
>>>>>         attrs = ['objectSID', 'tokenGroups']
>>>>> ...
>>>>>
>>>>> I think it's enough to avoid "optimizing" this process - we would find out
>>>>> the
>>>>> "optimization" soon anyway, as the tokenGroups search would return error 
>>>>> :-)
>>>>
>>>> Perfect!
>>>>
>>>> /me just had an eye vision exam, will complain to his doctor :-)
>>>>
>>>
>>> I enhanced the hbactest to also support user SID, not only trusted domain 
>>> user
>>> name. Updated set of patches attached.
>>>
>>> How it works:
>>>
>>> # ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-500 --host
>>> `hostname` --service sshd
>>> --------------------
>>> Access granted: True
>>> --------------------
>>>   Matched rules: can_login
>>>
>>> Martin
>>
>> Attaching a rebased set of patches.
> Thanks.
> 
> I think we need to account for few more conditions.
> 
> There are following cases:
> 
>  - user exists in AD
>  - user does not exist in AD
>  - group from AD specified as user
> 
> The latter case is interesting because we can have groups from AD in
> external group mappings and people may specify them by mistake in
> hbactest. In such case I think we could have stated that group/user
> unknown or unmapped.
> 
> Right now I'm getting less than helpful message:
> ---------------------------------------------------------------------------
> [root@jano ~]# ipa group-show extbar
>   Group name: extbar
>   Description: Ext bar
>   Member of groups: foobar
>   External member: S-1-5-21-3502988750-125904550-3683905862-512,
> S-1-5-21-3502988750-125904550-3683905862-500
> [root@jano ~]# ipa group-show foobar
>   Group name: foobar
>   Description: foo bar
>   GID: 944600004
>   Member groups: extbar
> [root@jano ~]# ipa hbactest --user 
> S-1-5-21-3502988750-125904550-3683905862-512
> --host `hostname` --service sshd
> ipa: ERROR: trusted domain object not found
> [root@jano ~]# ipa hbactest --user 
> S-1-5-21-3502988750-125904550-3683905862-500
> --host `hostname` --service sshd                              
> --------------------
> Access granted: True
> --------------------
>   Matched rules: allow_all
> [root@jano ~]# ipa hbactest --user ADX\\Administrator --host `hostname`
> --service sshd                                                       
> --------------------
> Access granted: True
> --------------------
>   Matched rules: allow_all
> [root@jano ~]# ipa hbactest --user ADX\\Domain\ Admins --host `hostname`
> --service sshd
> ipa: ERROR: trusted domain object not found
> [root@jano ~]# ipa hbactest --user ADX\\Enterprise\ Administrator --host
> `hostname` --service sshd
> ipa: ERROR: trusted domain object not found
> [root@jano ~]# ipa hbactest --user ADX\\DoesNotExist --host `hostname`
> --service sshd
> ipa: ERROR: trusted domain object not found
> -----------------------------------------------------------------------------
> 
> As you can see, group-as-user, unmapped group/user and non-existing AD
> user all raise error but the message is quite unclear -- does it talk
> about trusted domain itself or some object within the domain? Perhaps
> specifying real reason would be more helpful?
> 
> If we are expecting only a user object, then I think we can change
> message a bit to state it more clear.
> 
>> -                return False
>> -        return False
>> +            if not found_flatname:
>> +                raise errors.ValidationError(name=_('trusted domain 
>> object'),
>> +                        error= _('no trusted domain matched the specified
>> flat name'))
>> +        if not entries:
>> +            raise errors.NotFound(reason=_('trusted domain object not 
>> found'))
>> +
>> +        return entries
> 
> 

Fixed. This is quite straightforward as the errors are now returned via
specific exceptions and not with False/None result:

# ipa hbactest --user "AD\Doesnotexist" --host `hostname` --service sshdipa:
ERROR: trusted domain user not found

I am not convinced we should create a specific error when a group is passed
instead of user. To fix this, we would have to search GC with different filter
and also allow (|(objectclass=group)(objectclass=user)) instead of
(objectclass=user) which is there now and then check if the resulting object is
user or group. I am not convinced we want to do that, the error is now pretty
clear that user could not be found...

I also extended hbactest help and added a section about testing trusted domain
users, I forgot to add it before. I am also attaching a related simple patch
fixing formatting for other hbactest examples.

Martin
From 34366998033b8032d53301006602343110b0dd21 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Fri, 18 Jan 2013 17:28:39 +0100
Subject: [PATCH 1/3] Generalize AD GC search

Modify access methods to AD GC so that callers can specify a custom
basedn, filter, scope and attribute list, thus allowing it to perform
any LDAP search.

Error checking methodology in these functions was changed, so that it
rather raises an exception with a desription instead of simply returning
a None or False value which would made an investigation why something
does not work much more difficult. External membership method in
group-add-member command was updated to match this approach.

https://fedorahosted.org/freeipa/ticket/2997
---
 ipalib/plugins/group.py |   9 +--
 ipaserver/dcerpc.py     | 143 +++++++++++++++++++++++++++++++-----------------
 2 files changed, 99 insertions(+), 53 deletions(-)

diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py
index f86b134e61fc8c7518a64d25329babee3398c6ef..347a7ee9fda9cb574f433dff3a9621d8bffee887 100644
--- a/ipalib/plugins/group.py
+++ b/ipalib/plugins/group.py
@@ -384,11 +384,12 @@ class group_add_member(LDAPAddMember):
                 if domain_validator.is_trusted_sid_valid(sid):
                     sids.append(sid)
                 else:
-                    actual_sid = domain_validator.get_sid_trusted_domain_object(sid)
-                    if isinstance(actual_sid, unicode):
-                        sids.append(actual_sid)
+                    try:
+                        actual_sid = domain_validator.get_trusted_domain_object_sid(sid)
+                    except errors.PublicError, e:
+                        failed_sids.append((sid, unicode(e)))
                     else:
-                        failed_sids.append((sid, 'Not a trusted domain SID'))
+                        sids.append(actual_sid)
             if len(sids) == 0:
                 raise errors.ValidationError(name=_('external member'),
                                              error=_('values are not recognized as valid SIDs from trusted domain'))
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 384044437ec6ce66f3f69b593b9b4a2a3e945d3b..56d8b03198368f47746e5415abd73beb66e2300f 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -164,16 +164,18 @@ class DomainValidator(object):
         except errors.NotFound, e:
             return []
 
-    def is_trusted_sid_valid(self, sid):
+    def get_domain_by_sid(self, sid):
         if not self.domain:
             # our domain is not configured or self.is_configured() never run
             # reject SIDs as we can't check correctness of them
-            return False
+            raise errors.ValidationError(name='sid',
+                  error=_('domain is not configured'))
         # Parse sid string to see if it is really in a SID format
         try:
             test_sid = security.dom_sid(sid)
         except TypeError, e:
-            return False
+            raise errors.ValidationError(name='sid',
+                  error=_('SID is not valid'))
         # At this point we have SID_NT_AUTHORITY family SID and really need to
         # check it against prefixes of domain SIDs we trust to
         if not self._domains:
@@ -181,7 +183,8 @@ class DomainValidator(object):
         if len(self._domains) == 0:
             # Our domain is configured but no trusted domains are configured
             # This means we can't check the correctness of a trusted domain SIDs
-            return False
+            raise errors.ValidationError(name='sid',
+                  error=_('no trusted domain is configured'))
         # We have non-zero list of trusted domains and have to go through them
         # one by one and check their sids as prefixes
         test_sid_subauths = test_sid.sub_auths
@@ -190,44 +193,87 @@ class DomainValidator(object):
             sub_auths = domsid.sub_auths
             num_auths = min(test_sid.num_auths, domsid.num_auths)
             if test_sid_subauths[:num_auths] == sub_auths[:num_auths]:
-                return True
-        return False
+                return domain
+        raise errors.NotFound(reason=_('SID does not match any trusted domain'))
 
-    def get_sid_trusted_domain_object(self, object_name):
+    def is_trusted_sid_valid(self, sid):
+        try:
+            self.get_domain_by_sid(sid)
+        except (errors.ValidationError, errors.NotFound):
+            return False
+        else:
+            return True
+
+    def get_trusted_domain_objects(self, domain=None, flatname=None, filter="",
+            attrs=None, scope=_ldap.SCOPE_SUBTREE, basedn=None):
+        """
+        Search for LDAP objects in a trusted domain specified either by `domain'
+        or `flatname'. The actual LDAP search is specified by `filter', `attrs',
+        `scope' and `basedn'. When `basedn' is empty, database root DN is used.
+        """
+        assert domain is not None or flatname is not None
         """Returns SID for the trusted domain object (user or group only)"""
         if not self.domain:
             # our domain is not configured or self.is_configured() never run
-            return None
+            raise errors.ValidationError(name=_('Trust setup'),
+                error=_('Our domain is not configured'))
         if not self._domains:
             self._domains = self.get_trusted_domains()
         if len(self._domains) == 0:
             # Our domain is configured but no trusted domains are configured
-            return None
+            raise errors.ValidationError(name=_('Trust setup'),
+                error=_('No trusted domain is not configured'))
 
-        components = normalize_name(object_name)
-        if not ('domain' in components or 'flatname' in components):
-            # No domain or realm specified, ambiguous search
-            return False
-
-        entry = None
-        if 'domain' in components and components['domain'] in self._domains:
+        entries = None
+        if domain is not None:
+            if domain not in self._domains:
+                raise errors.ValidationError(name=_('trusted domain object'),
+                   error= _('domain is not trusted'))
             # Now we have a name to check against our list of trusted domains
-            entry = self.resolve_against_gc(components['domain'], components['name'])
-        elif 'flatname' in components:
+            entries = self.search_in_gc(domain, filter, attrs, scope, basedn)
+        elif flatname is not None:
             # Flatname was specified, traverse through the list of trusted
             # domains first to find the proper one
+            found_flatname = False
             for domain in self._domains:
-                if self._domains[domain][0] == components['flatname']:
-                    entry = self.resolve_against_gc(domain, components['name'])
-                    if entry:
+                if self._domains[domain][0] == flatname:
+                    found_flatname = True
+                    entries = self.search_in_gc(domain, filter, attrs, scope, basedn)
+                    if entries:
                         break
-        if entry:
-            try:
-                test_sid = security.dom_sid(entry)
-                return unicode(test_sid)
-            except TypeError, e:
-                return False
-        return False
+            if not found_flatname:
+                raise errors.ValidationError(name=_('trusted domain object'),
+                        error= _('no trusted domain matched the specified flat name'))
+        if not entries:
+            raise errors.NotFound(reason=_('trusted domain object not found'))
+
+        return entries
+
+    def get_trusted_domain_object_sid(self, object_name):
+        components = normalize_name(object_name)
+        if not ('domain' in components or 'flatname' in components):
+            # No domain or realm specified, ambiguous search
+             raise errors.ValidationError(name=_('trusted domain object'),
+                   error= _('Ambiguous search, user domain was not specified'))
+
+        attrs = ['objectSid']
+        filter = '(&(sAMAccountName=%(name)s)(|(objectClass=user)(objectClass=group)))' \
+                % dict(name=components['name'])
+        scope = _ldap.SCOPE_SUBTREE
+        entries = self.get_trusted_domain_objects(components.get('domain'),
+                components.get('flatname'), filter, attrs, scope)
+
+        if len(entries) > 1:
+            # Treat non-unique entries as invalid
+            raise errors.ValidationError(name=_('trusted domain object'),
+               error= _('Trusted domain did not return a unique object'))
+        sid = self.__sid_to_str(entries[0][1]['objectSid'][0])
+        try:
+            test_sid = security.dom_sid(sid)
+            return unicode(test_sid)
+        except TypeError, e:
+            raise errors.ValidationError(name=_('trusted domain object'),
+               error= _('Trusted domain did not return a valid SID for the object'))
 
     def __sid_to_str(self, sid):
         """
@@ -280,36 +326,33 @@ class DomainValidator(object):
                         dict(domain=info['dns_domain'],message=stderr.strip()))
             return (None, None)
 
-    def resolve_against_gc(self, domain, name):
+    def search_in_gc(self, domain, filter, attrs, scope, basedn=None):
         """
-        Resolves `name' against trusted domain `domain' using Global Catalog
-        Returns SID of the `name' or None
+        Perform LDAP search in a trusted domain `domain' Global Catalog.
+        Returns resulting entries or None
         """
-        entry = None
+        entries = None
         sid = None
         info = self.__retrieve_trusted_domain_gc_list(domain)
         if not info:
-            return None
+             raise errors.ValidationError(name=_('Trust setup'),
+                error=_('Cannot retrieve trusted domain GC list'))
         for (host, port) in info['gc']:
-            entry = self.__resolve_against_gc(info, host, port, name)
-            if entry:
+            entries = self.__search_in_gc(info, host, port, filter, attrs, scope, basedn)
+            if entries:
                 break
 
-        if entry:
-            l = len(entry)
-            if l > 2:
-                # Treat non-unique entries as invalid
-                return None
-            sid = self.__sid_to_str(entry[0][1]['objectSid'][0])
-        return sid
+        return entries
 
-    def __resolve_against_gc(self, info, host, port, name):
+    def __search_in_gc(self, info, host, port, filter, attrs, scope, basedn=None):
         """
-        Actual resolution against LDAP server, using SASL GSSAPI authentication
+        Actual search in AD LDAP server, using SASL GSSAPI authentication
         Returns LDAP result or None
         """
         conn = IPAdmin(host=host, port=port)
         auth = self.__extract_trusted_auth(info)
+        if attrs is None:
+            attrs = []
         if auth:
             (ccache_name, principal) = self.__kinit_as_trusted_account(info, auth)
             if ccache_name:
@@ -322,13 +365,15 @@ class DomainValidator(object):
                 # records pointing back to the same host name
                 conn.set_option(_ldap.OPT_X_SASL_NOCANON, _ldap.OPT_ON)
                 conn.sasl_interactive_bind_s(None, sasl_auth)
-                base = DN(*map(lambda p: ('dc', p), info['dns_domain'].split('.')))
+                if basedn is None:
+                    # Use domain root base DN
+                    basedn = DN(*map(lambda p: ('dc', p), info['dns_domain'].split('.')))
                 # We don't use conn.getEntry() because it will attempt to fetch schema from GC and that will fail
-                filterstr = conn.encode('(&(sAMAccountName=%(name)s)(|(objectClass=user)(objectClass=group)))' % dict(name=name))
-                attrlist = conn.encode(['sAMAccountName', 'sAMAccountType', 'objectSid', 'groupType', 'description'])
-                entry = conn.conn.search_s(str(base), _ldap.SCOPE_SUBTREE, filterstr, attrlist, 0)
+                filterstr = conn.encode(filter)
+                attrlist = conn.encode(attrs)
+                entries = conn.conn.search_s(str(basedn), scope, filterstr, attrlist, 0)
                 os.environ["KRB5CCNAME"] = old_ccache
-                return entry
+                return entries
 
     def __retrieve_trusted_domain_gc_list(self, domain):
         """
-- 
1.8.1.2

From be1065886df4dac9061981139ea1f625076dfad3 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Fri, 18 Jan 2013 17:33:19 +0100
Subject: [PATCH 2/3] Do not hide SID resolver error in group-add-member

When group-add-member does not receive any resolved trusted domain
object SID, it raises an exception which hides any useful error
message passed by underlying resolution methods. Remove the exception
to reveal this error messages to user.

https://fedorahosted.org/freeipa/ticket/2997
---
 ipalib/plugins/group.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py
index 347a7ee9fda9cb574f433dff3a9621d8bffee887..19404c6fad4b5ee21c1e00d2e799d552601fa29e 100644
--- a/ipalib/plugins/group.py
+++ b/ipalib/plugins/group.py
@@ -390,9 +390,6 @@ class group_add_member(LDAPAddMember):
                         failed_sids.append((sid, unicode(e)))
                     else:
                         sids.append(actual_sid)
-            if len(sids) == 0:
-                raise errors.ValidationError(name=_('external member'),
-                                             error=_('values are not recognized as valid SIDs from trusted domain'))
             restore = []
             if 'member' in failed and 'group' in failed['member']:
                 restore = failed['member']['group']
-- 
1.8.1.2

From 4bce2bfac1bf39c804d724e03ba7683fb2b1f259 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Fri, 18 Jan 2013 17:38:15 +0100
Subject: [PATCH 3/3] Add support for AD users to hbactest command

How this works:
  1. When a trusted domain user is tested, AD GC is searched
     for the user entry Distinguished Name
  2. The user entry is then read from AD GC and its SID and SIDs
     of all its assigned groups (tokenGroups attribute) are retrieved
  3. The SIDs are then used to search IPA LDAP database to find
     all external groups which have any of these SIDs as external
     members
  4. All these groups having these groups as direct or indirect
     members are added to hbactest allowing it to perform the search

LIMITATIONS:
- only Trusted Admins group members can use this function as it
  uses secret for IPA-Trusted domain link
- List of group SIDs does not contain group memberships outside
  of the trusted domain

https://fedorahosted.org/freeipa/ticket/2997
---
 ipalib/plugins/hbactest.py | 141 +++++++++++++++++++++++++++++++++++++++++----
 ipaserver/dcerpc.py        |  56 ++++++++++++++++++
 2 files changed, 187 insertions(+), 10 deletions(-)

diff --git a/ipalib/plugins/hbactest.py b/ipalib/plugins/hbactest.py
index 78fac0241de2ed6b7416029c692ee4bc53327b34..d5afe4415b412de765bec7861a54df0623cc8a75 100644
--- a/ipalib/plugins/hbactest.py
+++ b/ipalib/plugins/hbactest.py
@@ -17,11 +17,19 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from ipalib import api, errors, output
+from ipalib import api, errors, output, util
 from ipalib import Command, Str, Flag, Int
 from types import NoneType
 from ipalib.cli import to_cli
 from ipalib import _, ngettext
+from ipapython.dn import DN
+if api.env.in_server and api.env.context in ['lite', 'server']:
+    try:
+        import ipaserver.dcerpc
+        _dcerpc_bindings_installed = True
+    except ImportError:
+        _dcerpc_bindings_installed = False
+
 import pyhbac
 
 __doc__ = _("""
@@ -129,6 +137,74 @@ EXAMPLES:
       notmatched: new-rule
       matched: allow_all
 
+
+HBACTEST AND TRUSTED DOMAINS
+
+When an external trusted domain is configured in IPA, HBAC rules are also applied
+on users accessing IPA resources from the trusted domain. Trusted domain users and
+groups (and their SIDs) can be then assigned to external groups which can be
+members of POSIX groups in IPA which can be used in HBAC rules and thus allowing
+access to resources protected by the HBAC system.
+
+hbactest plugin is capable of testing access for both local IPA users and users
+from the trusted domains, either by a fully qualified user name or by user SID.
+Such user names need to have a trusted domain specified as a short name
+(DOMAIN\Administrator) or with a user principal name (UPN), administra...@ad.test.
+
+Please note that hbactest executed with a trusted domain user as --user parameter
+can be only run by members of "trust admins" group.
+
+EXAMPLES:
+
+    1. Test if a user from a trusted domain specified by its shortname matches any
+       rule:
+
+    $ ipa hbactest --user "DOMAIN\Administrator" --host `hostname` --service sshd
+    --------------------
+    Access granted: True
+    --------------------
+      Matched rules: allow_all
+      Matched rules: can_login
+
+    2. Test if a user from a trusted domain specified by its domain name matches
+       any rule:
+
+    $ ipa hbactest --user "administra...@domain.com" --host `hostname` --service sshd
+    --------------------
+    Access granted: True
+    --------------------
+      Matched rules: allow_all
+      Matched rules: can_login
+
+    3. Test if a user from a trusted domain specified by its SID matches any rule:
+
+    $ ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-500 \\
+            --host `hostname` --service sshd
+    --------------------
+    Access granted: True
+    --------------------
+      Matched rules: allow_all
+      Matched rules: can_login
+
+    4. Test if other user from a trusted domain specified by its SID matches any rule:
+
+    $ ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-500 \\
+            --host `hostname` --service sshd
+    --------------------
+    Access granted: True
+    --------------------
+      Matched rules: allow_all
+      Matched rules: can_login
+
+   5. Test if other user from a trusted domain specified by its shortname matches
+       any rule:
+
+    $ ipa hbactest --user "DOMAIN\Otheruser" --host `hostname` --service sshd
+    --------------------
+    Access granted: True
+    --------------------
+      Matched rules: allow_all
+      Not matched rules: can_login
 """)
 
 def convert_to_ipa_rule(rule):
@@ -298,15 +374,60 @@ class hbactest(Command):
         request = pyhbac.HbacRequest()
 
         if options['user'] != u'all':
-            try:
-                request.user.name = options['user']
-                search_result = self.api.Command.user_show(request.user.name)['result']
-                groups = search_result['memberof_group']
-                if 'memberofindirect_group' in search_result:
-                    groups += search_result['memberofindirect_group']
-                request.user.groups = sorted(set(groups))
-            except:
-                pass
+            # check first if this is not a trusted domain user
+            if _dcerpc_bindings_installed:
+                is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user'])
+            else:
+                is_valid_sid = False
+            components = util.normalize_name(options['user'])
+            if is_valid_sid or 'domain' in components or 'flatname' in components:
+                # this is a trusted domain user
+                if not _dcerpc_bindings_installed:
+                    raise errors.NotFound(reason=_(
+                        'Cannot perform external member validation without '
+                        'Samba 4 support installed. Make sure you have installed '
+                        'server-trust-ad sub-package of IPA on the server'))
+                domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
+                if not domain_validator.is_configured():
+                    raise errors.NotFound(reason=_(
+                        'Cannot search in trusted domains without own domain configured. '
+                        'Make sure you have run ipa-adtrust-install on the IPA server first'))
+                user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups(options['user'])
+                request.user.name = user_sid
+
+                # Now search for all external groups that have this user or
+                # any of its groups in its external members. Found entires
+                # memberOf links will be then used to gather all groups where
+                # this group is assigned, including the nested ones
+                filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \
+                        % ")(ipaExternalMember=".join(group_sids + [user_sid])
+
+                ldap = self.api.Backend.ldap2
+                group_container = DN(api.env.container_group, api.env.basedn)
+                try:
+                    entries, truncated = ldap.find_entries(filter_sids, ['cn', 'memberOf'], group_container)
+                except errors.NotFound:
+                    request.user.groups = []
+                else:
+                    groups = []
+                    for dn, entry in entries:
+                        memberof_dns = entry.get('memberof', [])
+                        for memberof_dn in memberof_dns:
+                            if memberof_dn.endswith(group_container):
+                                # this is a group object
+                                groups.append(memberof_dn[0][0].value)
+                    request.user.groups = sorted(set(groups))
+            else:
+                # try searching for a local user
+                try:
+                    request.user.name = options['user']
+                    search_result = self.api.Command.user_show(request.user.name)['result']
+                    groups = search_result['memberof_group']
+                    if 'memberofindirect_group' in search_result:
+                        groups += search_result['memberofindirect_group']
+                    request.user.groups = sorted(set(groups))
+                except:
+                    pass
 
         if options['service'] != u'all':
             try:
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 56d8b03198368f47746e5415abd73beb66e2300f..6243ebbb92e501dfb0919353d38a7a0af64183c0 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -275,6 +275,62 @@ class DomainValidator(object):
             raise errors.ValidationError(name=_('trusted domain object'),
                error= _('Trusted domain did not return a valid SID for the object'))
 
+    def get_trusted_domain_user_and_groups(self, object_name):
+        """
+        Returns a tuple with user SID and a list of SIDs of all groups he is
+        a member of.
+
+        LIMITATIONS:
+            - only Trusted Admins group members can use this function as it
+              uses secret for IPA-Trusted domain link
+            - List of group SIDs does not contain group memberships outside
+              of the trusted domain
+        """
+        components = normalize_name(object_name)
+        domain = components.get('domain')
+        flatname = components.get('flatname')
+        name = components.get('name')
+
+        is_valid_sid = is_sid_valid(object_name)
+        if is_valid_sid:
+            # Find a trusted domain for the SID
+            domain = self.get_domain_by_sid(object_name)
+            # Now search a trusted domain for a user with this SID
+            attrs = ['cn']
+            filter = '(&(objectClass=user)(objectSid=%(sid)s))' \
+                    % dict(sid=object_name)
+            try:
+                entries = self.get_trusted_domain_objects(domain=domain, filter=filter,
+                        attrs=attrs, scope=_ldap.SCOPE_SUBTREE)
+            except errors.NotFound:
+                raise errors.NotFound(reason=_('trusted domain user not found'))
+            user_dn = entries[0][0]
+        elif domain or flatname:
+            attrs = ['cn']
+            filter = '(&(sAMAccountName=%(name)s)(objectClass=user))' \
+                    % dict(name=name)
+            try:
+                entries = self.get_trusted_domain_objects(domain,
+                        flatname, filter, attrs, _ldap.SCOPE_SUBTREE)
+            except errors.NotFound:
+                raise errors.NotFound(reason=_('trusted domain user not found'))
+            user_dn = entries[0][0]
+        else:
+            # No domain or realm specified, ambiguous search
+            raise errors.ValidationError(name=_('trusted domain object'),
+                   error= _('Ambiguous search, user domain was not specified'))
+
+        # Get SIDs of user object and it's groups
+        # tokenGroups attribute must be read with a scope BASE for a known user
+        # distinguished name to avoid search error
+        attrs = ['objectSID', 'tokenGroups']
+        filter = "(objectClass=user)"
+        entries = self.get_trusted_domain_objects(domain,
+            flatname, filter, attrs, _ldap.SCOPE_BASE, user_dn)
+        object_sid = self.__sid_to_str(entries[0][1]['objectSid'][0])
+        group_sids = [self.__sid_to_str(sid) for sid in entries[0][1]['tokenGroups']]
+        return (object_sid, group_sids)
+
     def __sid_to_str(self, sid):
         """
         Converts binary SID to string representation
-- 
1.8.1.2

From 12f8b266ae9c54165935e5f86c93ef7abd904d70 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 13 Feb 2013 17:12:57 +0100
Subject: [PATCH] Fix hbachelp examples formatting

Add correct labeling of matched/nonmatched output attributes. Also
make sure that "\" is not interpreted as newline escape character
but really as a "\" character.
---
 ipalib/plugins/hbactest.py | 46 +++++++++++++++++++++++-----------------------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/ipalib/plugins/hbactest.py b/ipalib/plugins/hbactest.py
index d5afe4415b412de765bec7861a54df0623cc8a75..ed59c982d97fb012472fb0f29ac4dc6dae04dd91 100644
--- a/ipalib/plugins/hbactest.py
+++ b/ipalib/plugins/hbactest.py
@@ -77,10 +77,10 @@ EXAMPLES:
     --------------------
     Access granted: True
     --------------------
-      notmatched: my-second-rule
-      notmatched: my-third-rule
-      notmatched: myrule
-      matched: allow_all
+      Not matched rules: my-second-rule
+      Not matched rules: my-third-rule
+      Not matched rules: myrule
+      Matched rules: allow_all
 
     2. Disable detailed summary of how rules were applied:
     $ ipa hbactest --user=a1a --host=bar --service=sshd --nodetail
@@ -89,53 +89,53 @@ EXAMPLES:
     --------------------
 
     3. Test explicitly specified HBAC rules:
-    $ ipa hbactest --user=a1a --host=bar --service=sshd \
+    $ ipa hbactest --user=a1a --host=bar --service=sshd \\
           --rules=my-second-rule,myrule
     ---------------------
     Access granted: False
     ---------------------
-      notmatched: my-second-rule
-      notmatched: myrule
+      Not matched rules: my-second-rule
+      Not matched rules: myrule
 
     4. Use all enabled HBAC rules in IPA database + explicitly specified rules:
-    $ ipa hbactest --user=a1a --host=bar --service=sshd \
+    $ ipa hbactest --user=a1a --host=bar --service=sshd \\
           --rules=my-second-rule,myrule --enabled
     --------------------
     Access granted: True
     --------------------
-      notmatched: my-second-rule
-      notmatched: my-third-rule
-      notmatched: myrule
-      matched: allow_all
+      Not matched rules: my-second-rule
+      Not matched rules: my-third-rule
+      Not matched rules: myrule
+      Matched rules: allow_all
 
     5. Test all disabled HBAC rules in IPA database:
     $ ipa hbactest --user=a1a --host=bar --service=sshd --disabled
     ---------------------
     Access granted: False
     ---------------------
-      notmatched: new-rule
+      Not matched rules: new-rule
 
     6. Test all disabled HBAC rules in IPA database + explicitly specified rules:
-    $ ipa hbactest --user=a1a --host=bar --service=sshd \
+    $ ipa hbactest --user=a1a --host=bar --service=sshd \\
           --rules=my-second-rule,myrule --disabled
     ---------------------
     Access granted: False
     ---------------------
-      notmatched: my-second-rule
-      notmatched: my-third-rule
-      notmatched: myrule
+      Not matched rules: my-second-rule
+      Not matched rules: my-third-rule
+      Not matched rules: myrule
 
     7. Test all (enabled and disabled) HBAC rules in IPA database:
-    $ ipa hbactest --user=a1a --host=bar --service=sshd \
+    $ ipa hbactest --user=a1a --host=bar --service=sshd \\
           --enabled --disabled
     --------------------
     Access granted: True
     --------------------
-      notmatched: my-second-rule
-      notmatched: my-third-rule
-      notmatched: myrule
-      notmatched: new-rule
-      matched: allow_all
+      Not matched rules: my-second-rule
+      Not matched rules: my-third-rule
+      Not matched rules: myrule
+      Not matched rules: new-rule
+      Matched rules: allow_all
 
 
 HBACTEST AND TRUSTED DOMAINS
-- 
1.8.1.2

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to