diff options
-rw-r--r-- | API.txt | 25 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | install/updates/40-realm_domains.update | 8 | ||||
-rw-r--r-- | install/updates/Makefile.am | 1 | ||||
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/plugins/realmdomains.py | 141 | ||||
-rw-r--r-- | ipalib/util.py | 21 | ||||
-rw-r--r-- | tests/test_xmlrpc/objectclasses.py | 6 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_realmdomains_plugin.py | 165 |
9 files changed, 369 insertions, 1 deletions
@@ -2452,6 +2452,31 @@ 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: realmdomains_mod +args: 0,11,3 +option: Str('add_domain', attribute=True, autofill=False, cli_name='add_domain', multivalue=False, required=False) +option: Str('addattr*', cli_name='addattr', exclude='webui') +option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') +option: Str('associateddomain', attribute=True, autofill=False, cli_name='domain', multivalue=True, required=False) +option: Str('del_domain', attribute=True, autofill=False, cli_name='del_domain', multivalue=False, required=False) +option: Str('delattr*', cli_name='delattr', exclude='webui') +option: Flag('force', autofill=True, default=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: 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: realmdomains_show +args: 0,4,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: 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: role_add args: 1,6,3 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True) @@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=49 +IPA_API_VERSION_MINOR=50 diff --git a/install/updates/40-realm_domains.update b/install/updates/40-realm_domains.update new file mode 100644 index 000000000..9d766c676 --- /dev/null +++ b/install/updates/40-realm_domains.update @@ -0,0 +1,8 @@ +# Add the Realm Domains container + +dn: cn=Realm Domains,cn=ipa,cn=etc,$SUFFIX +default:objectClass: domainRelatedObject +default:objectClass: nsContainer +default:objectClass: top +default:cn: Realm Domains +default:associatedDomain: $DOMAIN diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index 2e4f0a264..ab3f4112a 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -28,6 +28,7 @@ app_DATA = \ 25-referint.update \ 30-s4u2proxy.update \ 40-delegation.update \ + 40-realm_domains.update \ 40-replication.update \ 40-dns.update \ 40-automember.update \ diff --git a/ipalib/constants.py b/ipalib/constants.py index e6d951440..ecb925582 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -108,6 +108,7 @@ DEFAULT_CONFIG = ( ('container_ranges', DN(('cn', 'ranges'), ('cn', 'etc'))), ('container_dna', DN(('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))), ('container_dna_posix_ids', DN(('cn', 'posix-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))), + ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))), # Ports, hosts, and URIs: # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri diff --git a/ipalib/plugins/realmdomains.py b/ipalib/plugins/realmdomains.py new file mode 100644 index 000000000..99ab8798b --- /dev/null +++ b/ipalib/plugins/realmdomains.py @@ -0,0 +1,141 @@ +# Authors: +# Ana Krivokapic <akrivoka@redhat.com> +# +# Copyright (C) 2013 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/>. + +from ipalib import api, errors +from ipalib import Str, Flag +from ipalib import _ +from ipalib.plugins.baseldap import LDAPObject, LDAPUpdate, LDAPRetrieve +from ipalib.plugins.dns import _domain_name_validator +from ipalib.util import has_soa_or_ns_record +from ipapython.dn import DN +from ipapython.ipautil import get_domain_name + + +__doc__ = _(""" +Realm domains + +Manage the list of domains associated with IPA realm. + +EXAMPLES: + + Display the current list of realm domains: + ipa realmdomains-show + + Replace the list of realm domains: + ipa realmdomains-mod --domain=example.com + ipa realmdomains-mod --domain={example1.com,example2.com,example3.com} + + Add a domain to the list of realm domains: + ipa realmdomains-mod --add-domain=newdomain.com + + Delete a domain from the list of realm domains: + ipa realmdomains-mod --del-domain=olddomain.com +""") + + +class realmdomains(LDAPObject): + """ + List of domains associated with IPA realm. + """ + container_dn = api.env.container_realm_domains + object_name = _('Realm domains') + search_attributes = ['associateddomain'] + default_attributes = ['associateddomain'] + + label = _('Realm Domains') + label_singular = _('Realm Domains') + + takes_params = ( + Str('associateddomain+', + _domain_name_validator, + cli_name='domain', + label=_('Domain'), + ), + Str('add_domain?', + _domain_name_validator, + cli_name='add_domain', + label=_('Add domain'), + ), + Str('del_domain?', + _domain_name_validator, + cli_name='del_domain', + label=_('Delete domain'), + ), + ) + +api.register(realmdomains) + + +class realmdomains_mod(LDAPUpdate): + __doc__ = _('Modify realm domains.') + + takes_options = LDAPUpdate.takes_options + ( + Flag('force', + label=_('Force'), + doc=_('Force adding domain even if not in DNS'), + ), + ) + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) + associateddomain = entry_attrs.get('associateddomain') + add_domain = entry_attrs.get('add_domain') + del_domain = entry_attrs.get('del_domain') + force = options.get('force') + + if associateddomain: + if add_domain or del_domain: + raise errors.MutuallyExclusiveError(reason=_("you cannot specify the --domain option together with --add-domain or --del-domain")) + if get_domain_name() not in associateddomain: + raise errors.ValidationError(name='domain', error=_("cannot delete domain of IPA server")) + if not force: + for d in associateddomain: + if not has_soa_or_ns_record(d): + raise errors.ValidationError(name='domain', error=_("no SOA or NS records found for domain %s" % d)) + return dn + + # If --add-domain or --del-domain options were provided, read + # the curent list from LDAP, modify it, and write the changes back + domains = ldap.get_entry(dn)[1]['associateddomain'] + + if add_domain: + if not force and not has_soa_or_ns_record(add_domain): + raise errors.ValidationError(name='add_domain', error=_("no SOA or NS records found for domain %s" % add_domain)) + del entry_attrs['add_domain'] + domains.append(add_domain) + + if del_domain: + if del_domain == get_domain_name(): + raise errors.ValidationError(name='del_domain', error=_("cannot delete domain of IPA server")) + del entry_attrs['del_domain'] + try: + domains.remove(del_domain) + except ValueError: + raise errors.AttrValueNotFound(attr='associateddomain', value=del_domain) + + entry_attrs['associateddomain'] = domains + return dn + +api.register(realmdomains_mod) + + +class realmdomains_show(LDAPRetrieve): + __doc__ = _('Display the list of realm domains.') + +api.register(realmdomains_show) diff --git a/ipalib/util.py b/ipalib/util.py index a92e68c4a..9ed27c84e 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -105,6 +105,27 @@ def validate_host_dns(log, fqdn): ) raise errors.DNSNotARecordError() + +def has_soa_or_ns_record(domain): + """ + Checks to see if given domain has SOA or NS record. + Returns True or False. + """ + try: + resolver.query(domain, rdatatype.SOA) + soa_record_found = True + except DNSException: + soa_record_found = False + + try: + resolver.query(domain, rdatatype.NS) + ns_record_found = True + except DNSException: + ns_record_found = False + + return soa_record_found or ns_record_found + + def normalize_name(name): result = dict() components = name.split('@') diff --git a/tests/test_xmlrpc/objectclasses.py b/tests/test_xmlrpc/objectclasses.py index a173bbe5c..d98a7ee64 100644 --- a/tests/test_xmlrpc/objectclasses.py +++ b/tests/test_xmlrpc/objectclasses.py @@ -154,3 +154,9 @@ dnsrecord = [ u'top', u'idnsrecord', ] + +realmdomains = [ + u'top', + u'nsContainer', + u'domainRelatedObject', +] diff --git a/tests/test_xmlrpc/test_realmdomains_plugin.py b/tests/test_xmlrpc/test_realmdomains_plugin.py new file mode 100644 index 000000000..112316382 --- /dev/null +++ b/tests/test_xmlrpc/test_realmdomains_plugin.py @@ -0,0 +1,165 @@ +# Authors: +# Ana Krivokapic <akrivoka@redhat.com> +# +# Copyright (C) 2013 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/realmdomains.py` module. +""" + +import random, string +from ipalib import api, errors +from ipapython.dn import DN +from tests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative + + +cn = u'Realm Domains' +dn = DN(('cn', cn), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) +our_domain = api.env.domain +new_domain_1 = u'example1.com' +new_domain_2 = u'example2.com' +bad_domain = u'this-domain-does-not-exist-%s.com' % ''.join(random.choice(string.lowercase) for x in range(10)) + + +class test_realmdomains(Declarative): + + cleanup_commands = [ + ('realmdomains_mod', [], {'associateddomain': [our_domain]}), + ] + + tests = [ + dict( + desc='Retrieve realm domains', + command=('realmdomains_show', [], {}), + expected=dict( + value=u'', + summary=None, + result=dict( + dn=dn, + associateddomain=[our_domain], + ), + ), + ), + dict( + desc='Retrieve realm domains - print all attributes', + command=('realmdomains_show', [], {'all': True}), + expected=dict( + value=u'', + summary=None, + result=dict( + dn=dn, + associateddomain=[our_domain], + cn=[cn], + objectclass=objectclasses.realmdomains, + ), + ), + ), + dict( + desc='Replace list of realm domains with "%s"' % [our_domain, new_domain_1], + command=('realmdomains_mod', [], {'associateddomain': [our_domain, new_domain_1]}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_1], + ), + ), + ), + dict( + desc='Add domain "%s" to list' % new_domain_2, + command=('realmdomains_mod', [], {'add_domain': new_domain_2}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_1, new_domain_2], + ), + ), + ), + dict( + desc='Delete domain "%s" from list' % new_domain_2, + command=('realmdomains_mod', [], {'del_domain': new_domain_2}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_1], + ), + ), + ), + dict( + desc='Add domain "%s" and delete domain "%s"' % (new_domain_2, new_domain_1), + command=('realmdomains_mod', [], {'add_domain': new_domain_2, 'del_domain': new_domain_1}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_2], + ), + ), + ), + dict( + desc='Try to specify --domain and --add-domain options together', + command=('realmdomains_mod', [], { + 'associateddomain': [our_domain, new_domain_1], + 'add_domain': new_domain_1, + }), + expected=errors.MutuallyExclusiveError( + reason='you cannot specify the --domain option together with --add-domain or --del-domain'), + ), + dict( + desc='Try to replace list of realm domains with a list without our domain', + command=('realmdomains_mod', [], {'associateddomain': [new_domain_1]}), + expected=errors.ValidationError( + name='domain', error='cannot delete domain of IPA server'), + ), + dict( + desc='Try to replace list of realm domains with a list with an invalid domain "%s"' % bad_domain, + command=('realmdomains_mod', [], {'associateddomain': [our_domain, bad_domain]}), + expected=errors.ValidationError( + name='domain', error='no SOA or NS records found for domain %s' % bad_domain), + ), + dict( + desc='Try to add an invalid domain "%s"' % bad_domain, + command=('realmdomains_mod', [], {'add_domain': bad_domain}), + expected=errors.ValidationError( + name='add_domain', error='no SOA or NS records found for domain %s' % bad_domain), + ), + dict( + desc='Try to delete our domain', + command=('realmdomains_mod', [], {'del_domain': our_domain}), + expected=errors.ValidationError( + name='del_domain', error='cannot delete domain of IPA server'), + ), + dict( + desc='Try to delete domain which is not in list', + command=('realmdomains_mod', [], {'del_domain': new_domain_1}), + expected=errors.AttrValueNotFound( + attr='associateddomain', value=new_domain_1), + ), + dict( + desc='Add an invalid domain "%s" with --force option' % bad_domain, + command=('realmdomains_mod', [], {'add_domain': bad_domain, 'force': True}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_2, bad_domain], + ), + ), + ), + ] |