On 02/01/2013 03:55 PM, Alexander Bokovoy wrote:
> On Tue, 29 Jan 2013, Martin Kosek wrote:
>> trust_output_params = (
>> @@ -482,3 +499,158 @@ api.register(trust_mod)
>> api.register(trust_del)
>> api.register(trust_find)
>> api.register(trust_show)
>> +
>> +
>> +_trust_type_option = (
>> +    StrEnum('trust_type',
>> +        cli_name='type',
>> +        label=_('Trust type (ad for Active Directory, default)'),
>> +        values=(u'ad',),
>> +        default=u'ad',
>> +        autofill=True,
>> +    ),
>> +)
> We already have various trust type definitions in the same file. Maybe
> it makes sense to unify those somehow?

Right, I unified those 2 separate trust_type option definitions.

> 
>> +    def get_dn(self, *keys, **kwargs):
>> +        trust_type = kwargs.get('trust_type')
>> +        if trust_type is None:
>> +            raise errors.RequirementError(name='trust_type')
>> +        if kwargs['trust_type'] == u'ad':
> Perhaps better to define constants for the trust type values...

I changed it a bit and now it uses a dict instead. I think its now more general
and extensible.

> 
>> +        except ValueError:
>> +            # The search is performed for groups with "posixgroup" 
>> objectclass
>> +            # and not "ipausergroup" so that it can also match groups like
>> +            # "Default SMG Group" which does not have this objectclass.
> 'Default SM_B_ Group'

Fixed.

> 
> Thanks for the unit tests too!
> 

You are welcome! I also generated API.txt which I forgot to do last time.
Updated patch attached.

Martin
From 5296910eef8ef46c179f9cb0b10c3ebcc1d90a9f Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Fri, 25 Jan 2013 10:10:17 +0100
Subject: [PATCH] Add trusconfig-show and trustconfig-mod commands

Global trust configuration is generated ipa-adtrust-install script
is run. Add convenience commands to show auto-generated options
like SID or GUID or options chosen by user (NetBIOS). Most of these
options are not modifiable via trustconfig-mod command as it would
break current trusts.

Unit test file covering these new commands was added.

https://fedorahosted.org/freeipa/ticket/3333
---
 API.txt                                |  24 +++++
 VERSION                                |   2 +-
 ipalib/plugins/trust.py                | 181 +++++++++++++++++++++++++++++++--
 tests/test_xmlrpc/test_trust_plugin.py | 159 +++++++++++++++++++++++++++++
 tests/test_xmlrpc/xmlrpc_test.py       |  10 ++
 5 files changed, 368 insertions(+), 8 deletions(-)
 create mode 100644 tests/test_xmlrpc/test_trust_plugin.py

diff --git a/API.txt b/API.txt
index 8fbfe6f5d8da44e991b8d1a36725fc6ace1f0616..6e5c8c5871bcfd320289291114c3c1534c400a54 100644
--- a/API.txt
+++ b/API.txt
@@ -3262,6 +3262,30 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, None)
+command: trustconfig_mod
+args: 0,9,3
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('ipantfallbackprimarygroup', attribute=True, autofill=False, cli_name='fallback_primary_group', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: StrEnum('trust_type', autofill=True, cli_name='type', default=u'ad', values=(u'ad',))
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
+command: trustconfig_show
+args: 0,5,3
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: StrEnum('trust_type', autofill=True, cli_name='type', default=u'ad', values=(u'ad',))
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
 command: user_add
 args: 1,34,3
 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, required=True)
diff --git a/VERSION b/VERSION
index 61f578dbfc9415f6f94a6612f198218c5a5e0c9a..37af5ef73b74500e0cd7397fb2c109332c049bc6 100644
--- a/VERSION
+++ b/VERSION
@@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=47
+IPA_API_VERSION_MINOR=48
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 2019d910b18ea507b9d05f5b6165e7b6d9a43e4e..6418c54a823f87a45bdd4779d35ab26f1ceb45d5 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -1,5 +1,6 @@
 # Authors:
 #     Alexander Bokovoy <aboko...@redhat.com>
+#     Martin Kosek <mko...@redhat.com>
 #
 # Copyright (C) 2011  Red Hat
 # see file 'COPYING' for use and warranty information
@@ -95,6 +96,22 @@ Example:
 4. List members of external members of ad_admins_external group to see their SIDs:
 
    ipa group-show ad_admins_external
+
+
+GLOBAL TRUST CONFIGURATION
+
+When IPA AD trust subpackage is installed and ipa-adtrust-install is run,
+a local domain configuration (SID, GUID, NetBIOS name) is generated. These
+identifiers are then used when communicating with a trusted domain of the
+particular type.
+
+1. Show global trust configuration for Active Directory type of trust
+
+   ipa trustconfig-show --type ad
+
+2. Modify global trust configuration and set different primary fallback group
+
+   ipa trustconfig-mod --type ad --fallback-primary-group "alternative AD group"
 """)
 
 trust_output_params = (
@@ -120,6 +137,14 @@ _trust_status_dict = {True : _('Established and verified'),
                  False : _('Waiting for confirmation by remote side')}
 _trust_type_dict_unknown = _('Unknown')
 
+_trust_type_option = StrEnum('trust_type',
+                        cli_name='type',
+                        label=_('Trust type (ad for Active Directory, default)'),
+                        values=(u'ad',),
+                        default=u'ad',
+                        autofill=True,
+                    )
+
 def trust_type_string(level):
     """
     Returns a string representing a type of the trust. The original field is an enum:
@@ -193,13 +218,7 @@ sides.
     ''')
 
     takes_options = LDAPCreate.takes_options + (
-        StrEnum('trust_type',
-            cli_name='type',
-            label=_('Trust type (ad for Active Directory, default)'),
-            values=(u'ad',),
-            default=u'ad',
-            autofill=True,
-        ),
+        _trust_type_option,
         Str('realm_admin?',
             cli_name='admin',
             label=_("Active Directory domain administrator"),
@@ -482,3 +501,151 @@ api.register(trust_mod)
 api.register(trust_del)
 api.register(trust_find)
 api.register(trust_show)
+
+_trustconfig_dn = {
+    u'ad': DN(('cn', api.env.domain), api.env.container_cifsdomains, api.env.basedn),
+}
+
+class trustconfig(LDAPObject):
+    """
+    Trusts global configuration object
+    """
+    object_name = _('trust configuration')
+    default_attributes = [
+        'cn', 'ipantsecurityidentifier', 'ipantflatname', 'ipantdomainguid',
+        'ipantfallbackprimarygroup',
+    ]
+
+    label = _('Global Trust Configuration')
+    label_singular = _('Global Trust Configuration')
+
+    takes_params = (
+        Str('cn',
+            label=_('Domain'),
+            flags=['no_update'],
+        ),
+        Str('ipantsecurityidentifier',
+            label=_('Security Identifier'),
+            flags=['no_update'],
+        ),
+        Str('ipantflatname',
+            label=_('NetBIOS name'),
+            flags=['no_update'],
+        ),
+        Str('ipantdomainguid',
+            label=_('Domain GUID'),
+            flags=['no_update'],
+        ),
+        Str('ipantfallbackprimarygroup',
+            cli_name='fallback_primary_group',
+            label=_('Fallback primary group'),
+        ),
+    )
+
+    def get_dn(self, *keys, **kwargs):
+        trust_type = kwargs.get('trust_type')
+        if trust_type is None:
+            raise errors.RequirementError(name='trust_type')
+        try:
+            return _trustconfig_dn[kwargs['trust_type']]
+        except KeyError:
+            raise errors.ValidationError(name='trust_type',
+                error=_("unsupported trust type"))
+
+    def _normalize_groupdn(self, entry_attrs):
+        """
+        Checks that group with given name/DN exists and updates the entry_attrs
+        """
+        if 'ipantfallbackprimarygroup' not in entry_attrs:
+            return
+
+        group = entry_attrs['ipantfallbackprimarygroup']
+        if isinstance(group, (list, tuple)):
+            group = group[0]
+
+        if group is None:
+            return
+
+        try:
+            dn = DN(group)
+            # group is in a form of a DN
+            try:
+                self.backend.get_entry(dn)
+            except errors.NotFound:
+                self.api.Object['group'].handle_not_found(group)
+            # DN is valid, we can just return
+            return
+        except ValueError:
+            # The search is performed for groups with "posixgroup" objectclass
+            # and not "ipausergroup" so that it can also match groups like
+            # "Default SMB Group" which does not have this objectclass.
+            try:
+                (dn, group_entry) = self.backend.find_entry_by_attr(
+                    self.api.Object['group'].primary_key.name,
+                    group,
+                    ['posixgroup'],
+                    [''],
+                    self.api.Object['group'].container_dn)
+            except errors.NotFound:
+                self.api.Object['group'].handle_not_found(group)
+            else:
+                entry_attrs['ipantfallbackprimarygroup'] = [dn]
+
+    def _convert_groupdn(self, entry_attrs, options):
+        """
+        Convert an group dn into a name. As we use CN as user RDN, its value
+        can be extracted from the DN without further LDAP queries.
+        """
+        if options.get('raw', False):
+            return
+
+        try:
+            groupdn = entry_attrs['ipantfallbackprimarygroup'][0]
+        except (IndexError, KeyError):
+            groupdn = None
+
+        if groupdn is None:
+            return
+        assert isinstance(groupdn, DN)
+
+        entry_attrs['ipantfallbackprimarygroup'] = [groupdn[0][0].value]
+
+api.register(trustconfig)
+
+class trustconfig_mod(LDAPUpdate):
+    __doc__ = _('Modify global trust configuration.')
+
+    takes_options = LDAPUpdate.takes_options + (_trust_type_option,)
+    msg_summary = _('Modified "%(value)s" trust configuration')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        self.obj._normalize_groupdn(entry_attrs)
+        return dn
+
+    def execute(self, *keys, **options):
+        result = super(trustconfig_mod, self).execute(*keys, **options)
+        result['value'] = options['trust_type']
+        return result
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj._convert_groupdn(entry_attrs, options)
+        return dn
+
+api.register(trustconfig_mod)
+
+
+class trustconfig_show(LDAPRetrieve):
+    __doc__ = _('Show global trust configuration.')
+
+    takes_options = LDAPRetrieve.takes_options + (_trust_type_option,)
+
+    def execute(self, *keys, **options):
+        result = super(trustconfig_show, self).execute(*keys, **options)
+        result['value'] = options['trust_type']
+        return result
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj._convert_groupdn(entry_attrs, options)
+        return dn
+
+api.register(trustconfig_show)
diff --git a/tests/test_xmlrpc/test_trust_plugin.py b/tests/test_xmlrpc/test_trust_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..7627be748137be181ce561aa774a1258b0ba253f
--- /dev/null
+++ b/tests/test_xmlrpc/test_trust_plugin.py
@@ -0,0 +1,159 @@
+# Authors:
+#   Martin Kosek <mko...@redhat.com>
+#
+# Copyright (C) 2010  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Test the `ipalib/plugins/trust.py` module.
+"""
+
+import nose
+from ipalib import api, errors
+from ipapython.dn import DN
+from tests.test_xmlrpc import objectclasses
+from xmlrpc_test import (Declarative, fuzzy_guid, fuzzy_domain_sid, fuzzy_string,
+        fuzzy_uuid, fuzzy_digits)
+
+
+trustconfig_ad_config = DN(('cn', api.env.domain),
+        api.env.container_cifsdomains, api.env.basedn)
+testgroup = u'adtestgroup'
+testgroup_dn = DN(('cn', testgroup), api.env.container_group, api.env.basedn)
+
+default_group = u'Default SMB Group'
+default_group_dn = DN(('cn', default_group), api.env.container_group, api.env.basedn)
+
+class test_trustconfig(Declarative):
+
+    @classmethod
+    def setUpClass(cls):
+        super(test_trustconfig, cls).setUpClass()
+        if not api.Backend.xmlclient.isconnected():
+            api.Backend.xmlclient.connect(fallback=False)
+        try:
+           api.Command['trustconfig_show'](trust_type=u'ad')
+        except errors.NotFound:
+            raise nose.SkipTest('Trusts are not configured')
+
+    cleanup_commands = [
+        ('group_del', [testgroup], {}),
+        ('trustconfig_mod', [], {'trust_type': u'ad',
+            'ipantfallbackprimarygroup': default_group}),
+    ]
+
+    tests = [
+
+        dict(
+            desc='Retrieve trust configuration for AD domains',
+            command=('trustconfig_show', [], {'trust_type': u'ad'}),
+            expected={
+                'value': u'ad',
+                'summary': None,
+                'result': {
+                    'dn': trustconfig_ad_config,
+                    'cn': [api.env.domain],
+                    'ipantdomainguid': [fuzzy_guid],
+                    'ipantfallbackprimarygroup': [default_group],
+                    'ipantflatname': [fuzzy_string],
+                    'ipantsecurityidentifier': [fuzzy_domain_sid]
+                },
+            },
+        ),
+
+        dict(
+            desc='Retrieve trust configuration for AD domains with --raw',
+            command=('trustconfig_show', [], {'trust_type': u'ad', 'raw': True}),
+            expected={
+                'value': u'ad',
+                'summary': None,
+                'result': {
+                    'dn': trustconfig_ad_config,
+                    'cn': [api.env.domain],
+                    'ipantdomainguid': [fuzzy_guid],
+                    'ipantfallbackprimarygroup': [default_group_dn],
+                    'ipantflatname': [fuzzy_string],
+                    'ipantsecurityidentifier': [fuzzy_domain_sid]
+                },
+            },
+        ),
+
+        dict(
+            desc='Create auxiliary group %r' % testgroup,
+            command=(
+                'group_add', [testgroup], dict(description=u'Test group')
+            ),
+            expected=dict(
+                value=testgroup,
+                summary=u'Added group "%s"' % testgroup,
+                result=dict(
+                    cn=[testgroup],
+                    description=[u'Test group'],
+                    gidnumber=[fuzzy_digits],
+                    objectclass=objectclasses.group + [u'posixgroup'],
+                    ipauniqueid=[fuzzy_uuid],
+                    dn=testgroup_dn,
+                ),
+            ),
+        ),
+
+        dict(
+            desc='Try to change primary fallback group to nonexistent group',
+            command=('trustconfig_mod', [],
+                {'trust_type': u'ad', 'ipantfallbackprimarygroup': u'doesnotexist'}),
+            expected=errors.NotFound(reason=u'%s: group not found' % 'doesnotexist')
+        ),
+
+        dict(
+            desc='Try to change primary fallback group to nonexistent group DN',
+            command=('trustconfig_mod', [], {'trust_type': u'ad',
+                'ipantfallbackprimarygroup': u'cn=doesnotexist,dc=test'}),
+            expected=errors.NotFound(reason=u'%s: group not found' % 'cn=doesnotexist,dc=test')
+        ),
+
+        dict(
+            desc='Change primary fallback group to "%s"' % testgroup,
+            command=('trustconfig_mod', [], {'trust_type': u'ad',
+                'ipantfallbackprimarygroup': testgroup}),
+            expected={
+                'value': u'ad',
+                'summary': u'Modified "ad" trust configuration',
+                'result': {
+                    'cn': [api.env.domain],
+                    'ipantdomainguid': [fuzzy_guid],
+                    'ipantfallbackprimarygroup': [testgroup],
+                    'ipantflatname': [fuzzy_string],
+                    'ipantsecurityidentifier': [fuzzy_domain_sid]
+                },
+            },
+        ),
+
+        dict(
+            desc='Change primary fallback group back to "%s" using DN' % default_group,
+            command=('trustconfig_mod', [], {'trust_type': u'ad',
+                 'ipantfallbackprimarygroup': unicode(default_group_dn)}),
+            expected={
+                'value': u'ad',
+                'summary': u'Modified "ad" trust configuration',
+                'result': {
+                    'cn': [api.env.domain],
+                    'ipantdomainguid': [fuzzy_guid],
+                    'ipantfallbackprimarygroup': [default_group],
+                    'ipantflatname': [fuzzy_string],
+                    'ipantsecurityidentifier': [fuzzy_domain_sid]
+                },
+            },
+        ),
+    ]
diff --git a/tests/test_xmlrpc/xmlrpc_test.py b/tests/test_xmlrpc/xmlrpc_test.py
index 7c32be0db31d69373f988a2bb1ec7171679b36ae..610fa97c56639331c542c9f0d1da8be839a11a2c 100644
--- a/tests/test_xmlrpc/xmlrpc_test.py
+++ b/tests/test_xmlrpc/xmlrpc_test.py
@@ -40,6 +40,16 @@ fuzzy_uuid = Fuzzy(
     '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
 )
 
+# Matches trusted domain GUID, like u'463bf2be-3456-4a57-979e-120304f2a0eb'
+fuzzy_guid = fuzzy_uuid
+
+# Matches SID of a trusted domain
+# SID syntax: http://msdn.microsoft.com/en-us/library/ff632068.aspx
+_sid_identifier_authority = '(0x[0-9a-f]{1,12}|[0-9]{1,10})'
+fuzzy_domain_sid = Fuzzy(
+    '^S-1-5-21-%(idauth)s-%(idauth)s-%(idauth)s$' % dict(idauth=_sid_identifier_authority)
+)
+
 # Matches netgroup dn. Note (?i) at the beginning of the regexp is the ingnore case flag
 fuzzy_netgroupdn = Fuzzy(
     '(?i)ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},cn=ng,cn=alt,%s' % api.env.basedn
-- 
1.7.11.7

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

Reply via email to