From 94d457e83c172320707fbf13f7a1587dad128ece Mon Sep 17 00:00:00 2001 From: John Dennis Date: Sun, 13 May 2012 07:36:35 -0400 Subject: Use DN objects instead of strings * Convert every string specifying a DN into a DN object * Every place a dn was manipulated in some fashion it was replaced by the use of DN operators * Add new DNParam parameter type for parameters which are DN's * DN objects are used 100% of the time throughout the entire data pipeline whenever something is logically a dn. * Many classes now enforce DN usage for their attributes which are dn's. This is implmented via ipautil.dn_attribute_property(). The only permitted types for a class attribute specified to be a DN are either None or a DN object. * Require that every place a dn is used it must be a DN object. This translates into lot of:: assert isinstance(dn, DN) sprinkled through out the code. Maintaining these asserts is valuable to preserve DN type enforcement. The asserts can be disabled in production. The goal of 100% DN usage 100% of the time has been realized, these asserts are meant to preserve that. The asserts also proved valuable in detecting functions which did not obey their function signatures, such as the baseldap pre and post callbacks. * Moved ipalib.dn to ipapython.dn because DN class is shared with all components, not just the server which uses ipalib. * All API's now accept DN's natively, no need to convert to str (or unicode). * Removed ipalib.encoder and encode/decode decorators. Type conversion is now explicitly performed in each IPASimpleLDAPObject method which emulates a ldap.SimpleLDAPObject method. * Entity & Entry classes now utilize DN's * Removed __getattr__ in Entity & Entity clases. There were two problems with it. It presented synthetic Python object attributes based on the current LDAP data it contained. There is no way to validate synthetic attributes using code checkers, you can't search the code to find LDAP attribute accesses (because synthetic attriutes look like Python attributes instead of LDAP data) and error handling is circumscribed. Secondly __getattr__ was hiding Python internal methods which broke class semantics. * Replace use of methods inherited from ldap.SimpleLDAPObject via IPAdmin class with IPAdmin methods. Directly using inherited methods was causing us to bypass IPA logic. Mostly this meant replacing the use of search_s() with getEntry() or getList(). Similarly direct access of the LDAP data in classes using IPAdmin were replaced with calls to getValue() or getValues(). * Objects returned by ldap2.find_entries() are now compatible with either the python-ldap access methodology or the Entity/Entry access methodology. * All ldap operations now funnel through the common IPASimpleLDAPObject giving us a single location where we interface to python-ldap and perform conversions. * The above 4 modifications means we've greatly reduced the proliferation of multiple inconsistent ways to perform LDAP operations. We are well on the way to having a single API in IPA for doing LDAP (a long range goal). * All certificate subject bases are now DN's * DN objects were enhanced thusly: - find, rfind, index, rindex, replace and insert methods were added - AVA, RDN and DN classes were refactored in immutable and mutable variants, the mutable variants are EditableAVA, EditableRDN and EditableDN. By default we use the immutable variants preserving important semantics. To edit a DN cast it to an EditableDN and cast it back to DN when done editing. These issues are fully described in other documentation. - first_key_match was removed - DN equalty comparison permits comparison to a basestring * Fixed ldapupdate to work with DN's. This work included: - Enhance test_updates.py to do more checking after applying update. Add test for update_from_dict(). Convert code to use unittest classes. - Consolidated duplicate code. - Moved code which should have been in the class into the class. - Fix the handling of the 'deleteentry' update action. It's no longer necessary to supply fake attributes to make it work. Detect case where subsequent update applies a change to entry previously marked for deletetion. General clean-up and simplification of the 'deleteentry' logic. - Rewrote a couple of functions to be clearer and more Pythonic. - Added documentation on the data structure being used. - Simplfy the use of update_from_dict() * Removed all usage of get_schema() which was being called prior to accessing the .schema attribute of an object. If a class is using internal lazy loading as an optimization it's not right to require users of the interface to be aware of internal optimization's. schema is now a property and when the schema property is accessed it calls a private internal method to perform the lazy loading. * Added SchemaCache class to cache the schema's from individual servers. This was done because of the observation we talk to different LDAP servers, each of which may have it's own schema. Previously we globally cached the schema from the first server we connected to and returned that schema in all contexts. The cache includes controls to invalidate it thus forcing a schema refresh. * Schema caching is now senstive to the run time context. During install and upgrade the schema can change leading to errors due to out-of-date cached schema. The schema cache is refreshed in these contexts. * We are aware of the LDAP syntax of all LDAP attributes. Every attribute returned from an LDAP operation is passed through a central table look-up based on it's LDAP syntax. The table key is the LDAP syntax it's value is a Python callable that returns a Python object matching the LDAP syntax. There are a handful of LDAP attributes whose syntax is historically incorrect (e.g. DistguishedNames that are defined as DirectoryStrings). The table driven conversion mechanism is augmented with a table of hard coded exceptions. Currently only the following conversions occur via the table: - dn's are converted to DN objects - binary objects are converted to Python str objects (IPA convention). - everything else is converted to unicode using UTF-8 decoding (IPA convention). However, now that the table driven conversion mechanism is in place it would be trivial to do things such as converting attributes which have LDAP integer syntax into a Python integer, etc. * Expected values in the unit tests which are a DN no longer need to use lambda expressions to promote the returned value to a DN for equality comparison. The return value is automatically promoted to a DN. The lambda expressions have been removed making the code much simpler and easier to read. * Add class level logging to a number of classes which did not support logging, less need for use of root_logger. * Remove ipaserver/conn.py, it was unused. * Consolidated duplicate code wherever it was found. * Fixed many places that used string concatenation to form a new string rather than string formatting operators. This is necessary because string formatting converts it's arguments to a string prior to building the result string. You can't concatenate a string and a non-string. * Simplify logic in rename_managed plugin. Use DN operators to edit dn's. * The live version of ipa-ldap-updater did not generate a log file. The offline version did, now both do. https://fedorahosted.org/freeipa/ticket/1670 https://fedorahosted.org/freeipa/ticket/1671 https://fedorahosted.org/freeipa/ticket/1672 https://fedorahosted.org/freeipa/ticket/1673 https://fedorahosted.org/freeipa/ticket/1674 https://fedorahosted.org/freeipa/ticket/1392 https://fedorahosted.org/freeipa/ticket/2872 --- API.txt | 14 +- contrib/RHEL4/ipa-client-setup | 8 +- .../certmonger/dogtag-ipa-retrieve-agent-submit | 4 +- install/migration/migration.py | 5 +- install/restart_scripts/renew_ca_cert | 4 +- install/restart_scripts/renew_ra_cert | 8 +- install/tools/ipa-adtrust-install | 1 + install/tools/ipa-compat-manage | 7 +- install/tools/ipa-compliance | 5 +- install/tools/ipa-csreplica-manage | 41 +- install/tools/ipa-dns-install | 2 +- install/tools/ipa-managed-entries | 19 +- install/tools/ipa-nis-manage | 7 +- install/tools/ipa-replica-install | 19 +- install/tools/ipa-replica-manage | 69 +- install/tools/ipa-replica-prepare | 14 +- install/tools/ipa-server-certinstall | 5 +- install/tools/ipa-server-install | 18 +- install/tools/ipa-upgradeconfig | 2 +- install/tools/ipactl | 15 +- ipa-client/ipa-install/ipa-client-automount | 2 +- ipa-client/ipa-install/ipa-client-install | 18 +- ipa-client/ipaclient/ipadiscovery.py | 4 +- ipalib/__init__.py | 2 +- ipalib/aci.py | 26 - ipalib/config.py | 5 +- ipalib/constants.py | 73 +- ipalib/dn.py | 1337 -------------- ipalib/encoder.py | 213 --- ipalib/parameters.py | 19 +- ipalib/plugins/aci.py | 52 +- ipalib/plugins/automember.py | 25 +- ipalib/plugins/automount.py | 6 +- ipalib/plugins/baseldap.py | 188 +- ipalib/plugins/config.py | 7 +- ipalib/plugins/dns.py | 34 +- ipalib/plugins/entitle.py | 9 +- ipalib/plugins/group.py | 8 + ipalib/plugins/hbacrule.py | 9 + ipalib/plugins/host.py | 15 +- ipalib/plugins/hostgroup.py | 8 +- ipalib/plugins/krbtpolicy.py | 5 +- ipalib/plugins/migration.py | 86 +- ipalib/plugins/netgroup.py | 7 + ipalib/plugins/passwd.py | 3 +- ipalib/plugins/permission.py | 19 +- ipalib/plugins/pkinit.py | 5 +- ipalib/plugins/pwpolicy.py | 26 +- ipalib/plugins/range.py | 8 +- ipalib/plugins/selinuxusermap.py | 7 + ipalib/plugins/service.py | 7 + ipalib/plugins/sudorule.py | 16 + ipalib/plugins/trust.py | 8 +- ipalib/plugins/user.py | 18 +- ipalib/plugins/virtual.py | 3 +- ipalib/rpc.py | 5 +- ipalib/session.py | 2 +- ipalib/util.py | 9 +- ipalib/x509.py | 2 +- ipapython/dn.py | 1643 +++++++++++++++++ ipapython/entity.py | 32 +- ipapython/ipa_log_manager.py | 4 +- ipapython/ipautil.py | 43 +- ipaserver/conn.py | 69 - ipaserver/dcerpc.py | 6 +- ipaserver/install/adtrustinstance.py | 63 +- ipaserver/install/bindinstance.py | 28 +- ipaserver/install/cainstance.py | 64 +- ipaserver/install/certs.py | 26 +- ipaserver/install/dsinstance.py | 42 +- ipaserver/install/httpinstance.py | 2 + ipaserver/install/installutils.py | 5 +- ipaserver/install/ipa_ldap_updater.py | 2 + ipaserver/install/krbinstance.py | 23 +- ipaserver/install/ldapupdate.py | 688 +++---- ipaserver/install/plugins/adtrust.py | 16 +- ipaserver/install/plugins/dns.py | 41 +- ipaserver/install/plugins/fix_replica_memberof.py | 4 +- ipaserver/install/plugins/rename_managed.py | 159 +- ipaserver/install/plugins/updateclient.py | 23 +- ipaserver/install/replication.py | 204 ++- ipaserver/install/service.py | 26 +- ipaserver/ipaldap.py | 115 +- ipaserver/plugins/dogtag.py | 14 +- ipaserver/plugins/ldap2.py | 1123 ++++++++---- ipaserver/plugins/selfsign.py | 11 +- ipaserver/rpcserver.py | 34 +- make-lint | 4 + make-testcert | 5 +- tests/test_cmdline/test_ipagetkeytab.py | 2 +- tests/test_install/0_reset.update | 2 +- tests/test_install/test_updates.py | 238 ++- tests/test_ipalib/test_dn.py | 1085 ----------- tests/test_ipalib/test_encoder.py | 149 -- tests/test_ipalib/test_x509.py | 2 +- tests/test_ipapython/test_dn.py | 1937 ++++++++++++++++++++ tests/test_ipaserver/test_changepw.py | 6 +- tests/test_ipaserver/test_ldap.py | 11 +- tests/test_xmlrpc/test_attr.py | 104 +- tests/test_xmlrpc/test_automember_plugin.py | 129 +- tests/test_xmlrpc/test_automount_plugin.py | 36 +- tests/test_xmlrpc/test_batch_plugin.py | 20 +- tests/test_xmlrpc/test_cert.py | 2 +- tests/test_xmlrpc/test_delegation_plugin.py | 11 +- tests/test_xmlrpc/test_dns_plugin.py | 59 +- tests/test_xmlrpc/test_group_plugin.py | 72 +- tests/test_xmlrpc/test_hbacsvcgroup_plugin.py | 16 +- tests/test_xmlrpc/test_host_plugin.py | 44 +- tests/test_xmlrpc/test_hostgroup_plugin.py | 29 +- tests/test_xmlrpc/test_krbtpolicy.py | 21 +- tests/test_xmlrpc/test_nesting.py | 166 +- tests/test_xmlrpc/test_netgroup_plugin.py | 53 +- tests/test_xmlrpc/test_permission_plugin.py | 97 +- tests/test_xmlrpc/test_privilege_plugin.py | 30 +- tests/test_xmlrpc/test_range_plugin.py | 12 +- tests/test_xmlrpc/test_replace.py | 17 +- tests/test_xmlrpc/test_role_plugin.py | 44 +- tests/test_xmlrpc/test_selinuxusermap_plugin.py | 36 +- tests/test_xmlrpc/test_service_plugin.py | 38 +- tests/test_xmlrpc/test_sudocmd_plugin.py | 22 +- tests/test_xmlrpc/test_sudocmdgroup_plugin.py | 102 +- tests/test_xmlrpc/test_user_plugin.py | 231 +-- tests/test_xmlrpc/xmlrpc_test.py | 4 +- tests/util.py | 4 + 124 files changed, 6627 insertions(+), 5286 deletions(-) delete mode 100644 ipalib/dn.py delete mode 100644 ipalib/encoder.py create mode 100644 ipapython/dn.py delete mode 100644 ipaserver/conn.py delete mode 100644 tests/test_ipalib/test_dn.py delete mode 100644 tests/test_ipalib/test_encoder.py create mode 100644 tests/test_ipapython/test_dn.py diff --git a/API.txt b/API.txt index 6f14fccc3..d32d6393b 100644 --- a/API.txt +++ b/API.txt @@ -491,7 +491,7 @@ output: Output('value', , None) command: cosentry_add args: 1,7,3 arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, required=True) -option: Str('krbpwdpolicyreference', attribute=True, cli_name='krbpwdpolicyreference', multivalue=False, required=True) +option: DNParam('krbpwdpolicyreference', attribute=True, cli_name='krbpwdpolicyreference', multivalue=False, required=True) option: Int('cospriority', attribute=True, cli_name='cospriority', minvalue=0, multivalue=False, required=True) option: Str('setattr*', cli_name='setattr', exclude='webui') option: Str('addattr*', cli_name='addattr', exclude='webui') @@ -512,7 +512,7 @@ command: cosentry_find args: 1,9,4 arg: Str('criteria?', noextrawhitespace=False) option: Str('cn', attribute=True, autofill=False, cli_name='cn', multivalue=False, primary_key=True, query=True, required=False) -option: Str('krbpwdpolicyreference', attribute=True, autofill=False, cli_name='krbpwdpolicyreference', multivalue=False, query=True, required=False) +option: DNParam('krbpwdpolicyreference', attribute=True, autofill=False, cli_name='krbpwdpolicyreference', multivalue=False, query=True, required=False) option: Int('cospriority', attribute=True, autofill=False, cli_name='cospriority', minvalue=0, multivalue=False, query=True, required=False) option: Int('timelimit?', autofill=False, minvalue=0) option: Int('sizelimit?', autofill=False, minvalue=0) @@ -527,7 +527,7 @@ output: Output('truncated', , None) command: cosentry_mod args: 1,9,3 arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, query=True, required=True) -option: Str('krbpwdpolicyreference', attribute=True, autofill=False, cli_name='krbpwdpolicyreference', multivalue=False, required=False) +option: DNParam('krbpwdpolicyreference', attribute=True, autofill=False, cli_name='krbpwdpolicyreference', multivalue=False, required=False) option: Int('cospriority', attribute=True, autofill=False, cli_name='cospriority', minvalue=0, multivalue=False, required=False) option: Str('setattr*', cli_name='setattr', exclude='webui') option: Str('addattr*', cli_name='addattr', exclude='webui') @@ -1931,9 +1931,9 @@ command: migrate_ds args: 2,16,4 arg: Str('ldapuri', cli_name='ldap_uri') arg: Password('bindpw', cli_name='password', confirm=False) -option: Str('binddn?', autofill=True, cli_name='bind_dn', default=u'cn=directory manager') -option: Str('usercontainer', autofill=True, cli_name='user_container', default=u'ou=people') -option: Str('groupcontainer', autofill=True, cli_name='group_container', default=u'ou=groups') +option: DNParam('binddn?', autofill=True, cli_name='bind_dn', default=ipapython.dn.DN('cn=directory manager')) +option: DNParam('usercontainer', autofill=True, cli_name='user_container', default=ipapython.dn.DN('ou=people')) +option: DNParam('groupcontainer', autofill=True, cli_name='group_container', default=ipapython.dn.DN('ou=groups')) option: Str('userobjectclass+', autofill=True, cli_name='user_objectclass', csv=True, default=(u'person',)) option: Str('groupobjectclass+', autofill=True, cli_name='group_objectclass', csv=True, default=(u'groupOfUniqueNames', u'groupOfNames')) option: Str('userignoreobjectclass*', autofill=True, cli_name='user_ignore_objectclass', csv=True, default=()) @@ -1943,7 +1943,7 @@ option: Str('groupignoreattribute*', autofill=True, cli_name='group_ignore_attri option: Flag('groupoverwritegid', autofill=True, cli_name='group_overwrite_gid', default=False) option: StrEnum('schema?', autofill=True, cli_name='schema', default=u'RFC2307bis', values=(u'RFC2307bis', u'RFC2307')) option: Flag('continue?', autofill=True, default=False) -option: Str('basedn?', cli_name='base_dn') +option: DNParam('basedn?', cli_name='base_dn') option: Flag('compat?', autofill=True, cli_name='with_compat', default=False) option: Str('exclude_groups*', autofill=True, cli_name='exclude_groups', csv=True, default=()) option: Str('exclude_users*', autofill=True, cli_name='exclude_users', csv=True, default=()) diff --git a/contrib/RHEL4/ipa-client-setup b/contrib/RHEL4/ipa-client-setup index bba60c2ab..1a8761036 100644 --- a/contrib/RHEL4/ipa-client-setup +++ b/contrib/RHEL4/ipa-client-setup @@ -31,6 +31,7 @@ from optparse import OptionParser import ipachangeconf import ldap from ldap import LDAPError +from ipapython.dn import DN class ipaserver: @@ -90,7 +91,8 @@ class ipaserver: #search and return known realms root_logger.debug("Search for (objectClass=krbRealmContainer) in "+self.basedn+"(sub)") - lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") + lret = lh.search_s(str(DN(('cn', 'kerberos'), self.basedn)), + ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") if not lret: #something very wrong return False @@ -272,8 +274,8 @@ def main(): {'name':'ldap_version', 'type':'option', 'value':'3'}, {'name':'base', 'type':'option', 'value':ipasrv.getBaseDN()}, {'name':'empty', 'type':'empty'}, - {'name':'nss_base_passwd', 'type':'option', 'value':'cn=users,cn=accounts,'+ipasrv.getBaseDN()+'?sub'}, - {'name':'nss_base_group', 'type':'option', 'value':'cn=users,cn=accounts,'+ipasrv.getBaseDN()+'?sub'}, + {'name':'nss_base_passwd', 'type':'option', 'value':str(DN(('cn', 'users'), ('cn', 'accounts'), ipasrv.getBaseDN()))+'?sub'}, + {'name':'nss_base_group', 'type':'option', 'value':str(DN(('cn', 'users'), ('cn', 'accounts'), ipasrv.getBaseDN()))+'?sub'}, {'name':'nss_schema', 'type':'option', 'value':'rfc2307bis'}, {'name':'nss_map_attribute', 'type':'option', 'value':'uniqueMember member'}, {'name':'nss_initgroups_ignoreusers', 'type':'option', 'value':'root,dirsrv'}, diff --git a/install/certmonger/dogtag-ipa-retrieve-agent-submit b/install/certmonger/dogtag-ipa-retrieve-agent-submit index 24e1844a5..6d54000d6 100644 --- a/install/certmonger/dogtag-ipa-retrieve-agent-submit +++ b/install/certmonger/dogtag-ipa-retrieve-agent-submit @@ -29,7 +29,7 @@ import tempfile import krbV import syslog from ipalib import api -from ipalib.dn import DN +from ipapython.dn import DN from ipalib import errors from ipalib import x509 from ipapython import services as ipaservices @@ -52,7 +52,7 @@ api.finalize() # Update or add it tmpdir = tempfile.mkdtemp(prefix = "tmp-") try: - dn = str(DN(('cn',nickname),('cn=ca_renewal,cn=ipa,cn=etc'),(api.env.basedn))) + dn = DN(('cn', nickname), ('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) principal = str('host/%s@%s' % (api.env.host, api.env.realm)) ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir, principal) conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri) diff --git a/install/migration/migration.py b/install/migration/migration.py index 449d35c66..e0ada1310 100644 --- a/install/migration/migration.py +++ b/install/migration/migration.py @@ -27,6 +27,7 @@ import ldap import wsgiref from ipapython.ipa_log_manager import * from ipapython.ipautil import get_ipa_basedn +from ipapython.dn import DN BASE_DN = '' LDAP_URI = 'ldaps://localhost:636' @@ -80,10 +81,10 @@ def bind(username, password): if not base_dn: root_logger.error('migration unable to get base dn') raise IOError(errno.EIO, 'Cannot get Base DN') - bind_dn = 'uid=%s,cn=users,cn=accounts,%s' % (username, base_dn) + bind_dn = DN(('uid', username), ('cn', 'users'), ('cn', 'accounts'), base_dn) try: conn = ldap.initialize(LDAP_URI) - conn.simple_bind_s(bind_dn, password) + conn.simple_bind_s(str(bind_dn), password) except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM, ldap.NO_SUCH_OBJECT), e: root_logger.error('migration invalid credentials for %s: %s' % (bind_dn, convert_exception(e))) diff --git a/install/restart_scripts/renew_ca_cert b/install/restart_scripts/renew_ca_cert index d3b756042..e4374eca5 100644 --- a/install/restart_scripts/renew_ca_cert +++ b/install/restart_scripts/renew_ca_cert @@ -26,7 +26,7 @@ import tempfile import krbV import syslog from ipalib import api -from ipalib.dn import DN +from ipapython.dn import DN from ipalib import errors from ipapython import services as ipaservices from ipapython import ipautil @@ -50,7 +50,7 @@ if not cert: # Update or add it tmpdir = tempfile.mkdtemp(prefix = "tmp-") try: - dn = str(DN(('cn',nickname),('cn=ca_renewal,cn=ipa,cn=etc'),(api.env.basedn))) + dn = DN(('cn',nickname), ('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) principal = str('host/%s@%s' % (api.env.host, api.env.realm)) ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir, principal) conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri) diff --git a/install/restart_scripts/renew_ra_cert b/install/restart_scripts/renew_ra_cert index 2fcf1a79b..14cbc114c 100644 --- a/install/restart_scripts/renew_ra_cert +++ b/install/restart_scripts/renew_ra_cert @@ -29,7 +29,7 @@ from ipapython import ipautil from ipaserver.install import certs from ipaserver.install.cainstance import DEFAULT_DSPORT from ipalib import api -from ipalib.dn import DN +from ipapython.dn import DN from ipalib import x509 from ipalib import errors from ipaserver.plugins.ldap2 import ldap2 @@ -45,7 +45,7 @@ subject = x509.get_subject(cert, datatype=x509.DER) issuer = x509.get_issuer(cert, datatype=x509.DER) # Load it into dogtag -dn = str(DN(('uid','ipara'),('ou','People'),('o','ipaca'))) +dn = DN(('uid','ipara'),('ou','People'),('o','ipaca')) try: dm_password = get_pin('internaldb') @@ -55,7 +55,7 @@ except IOError, e: try: conn = ldap2(shared_instance=False, ldap_uri='ldap://localhost:%d' % DEFAULT_DSPORT) - conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) + conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password) (entry_dn, entry_attrs) = conn.get_entry(dn, ['usercertificate'], normalize=False) entry_attrs['usercertificate'].append(cert) entry_attrs['description'] = '2;%d;%s;%s' % (serial_number, issuer, subject) @@ -68,7 +68,7 @@ except Exception, e: # Store it in the IPA LDAP server tmpdir = tempfile.mkdtemp(prefix = "tmp-") try: - dn = str(DN(('cn','ipaCert'),('cn=ca_renewal,cn=ipa,cn=etc'),(api.env.basedn))) + dn = DN(('cn','ipaCert'), ('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) principal = str('host/%s@%s' % (api.env.host, api.env.realm)) ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir, principal) conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri) diff --git a/install/tools/ipa-adtrust-install b/install/tools/ipa-adtrust-install index 02a309306..8cea077cc 100755 --- a/install/tools/ipa-adtrust-install +++ b/install/tools/ipa-adtrust-install @@ -32,6 +32,7 @@ from ipapython.config import IPAOptionParser import krbV import ldap from ipapython.ipa_log_manager import * +from ipapython.dn import DN log_file_name = "/var/log/ipaserver-install.log" diff --git a/install/tools/ipa-compat-manage b/install/tools/ipa-compat-manage index f7564e0c5..e88d92286 100755 --- a/install/tools/ipa-compat-manage +++ b/install/tools/ipa-compat-manage @@ -28,6 +28,7 @@ try: from ipaserver.plugins.ldap2 import ldap2 from ipalib import api, errors from ipapython.ipa_log_manager import * + from ipapython.dn import DN except ImportError: print >> sys.stderr, """\ There was a problem importing one of the required Python modules. The @@ -37,8 +38,8 @@ error was: """ % sys.exc_value sys.exit(1) -compat_dn = "cn=Schema Compatibility,cn=plugins,cn=config" -nis_config_dn = "cn=NIS Server,cn=plugins,cn=config" +compat_dn = DN(('cn', 'Schema Compatibility'), ('cn', 'plugins'), ('cn', 'config')) +nis_config_dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config')) def parse_options(): usage = "%prog [options] \n" @@ -107,7 +108,7 @@ def main(): try: conn = ldap2(shared_instance=False, base_dn='') conn.connect( - bind_dn='cn=directory manager', bind_pw=dirman_password + bind_dn=DN(('cn', 'directory manager')), bind_pw=dirman_password ) except errors.ExecutionError, lde: sys.exit("An error occurred while connecting to the server.\n%s\n" % str(lde)) diff --git a/install/tools/ipa-compliance b/install/tools/ipa-compliance index 8ae91b777..c82e4151f 100644 --- a/install/tools/ipa-compliance +++ b/install/tools/ipa-compliance @@ -36,6 +36,7 @@ try: from ipaserver.plugins.ldap2 import ldap2 from ipalib import api, errors, backend from ipaserver.install import installutils + from ipapython.dn import DN except ImportError, e: # If python-rhsm isn't installed exit gracefully and quietly. if e.args[0] == 'No module named rhsm.certificate': @@ -116,7 +117,7 @@ def check_compliance(tmpdir, debug=False): # Get the hosts first try: (entries, truncated) = conn.find_entries('(krblastpwdchange=*)', ['dn'], - '%s,%s' % (api.env.container_host, api.env.basedn), + DN(api.env.container_host, api.env.basedn), conn.SCOPE_ONELEVEL, size_limit = -1) except errors.NotFound: @@ -136,7 +137,7 @@ def check_compliance(tmpdir, debug=False): try: (entries, truncated) = conn.find_entries('(objectclass=ipaentitlement)', ['dn', 'userCertificate'], - '%s,%s' % (api.env.container_entitlements, api.env.basedn), + DN(api.env.container_entitlements, api.env.basedn), conn.SCOPE_ONELEVEL, size_limit = -1) diff --git a/install/tools/ipa-csreplica-manage b/install/tools/ipa-csreplica-manage index 938201fed..6eefe8d6d 100755 --- a/install/tools/ipa-csreplica-manage +++ b/install/tools/ipa-csreplica-manage @@ -30,7 +30,7 @@ from ipaserver.install import replication, installutils from ipaserver import ipaldap from ipapython import version from ipalib import api, errors, util -from ipalib.dn import DN +from ipapython.dn import DN CACERT = "/etc/ipa/ca.crt" PORT = 7389 @@ -63,7 +63,7 @@ class CSReplicationManager(replication.ReplicationManager): def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=True): super(CSReplicationManager, self).__init__(realm, hostname, dirman_passwd, port, starttls) - self.suffix = 'o=ipaca' + self.suffix = DN(('o', 'ipaca')) self.hostnames = [] # set before calling or agreement_dn() will fail def agreement_dn(self, hostname, master=None): @@ -89,14 +89,14 @@ class CSReplicationManager(replication.ReplicationManager): else: name = "clone" cn="%sAgreement1-%s-%s" % (name, hostname, instance_name) - dn = str(DN("cn=%s, %s" % (cn, self.replica_dn()))) + dn = DN(('cn', cn), self.replica_dn()) return (cn, dn) for host in self.hostnames: for master in ["master", "clone"]: try: cn="%sAgreement1-%s-%s" % (master, host, instance_name) - dn = "cn=%s, %s" % (cn, self.replica_dn()) + dn = DN(('cn', cn), self.replica_dn()) self.conn.getEntry(dn, ldap.SCOPE_BASE) return (cn, dn) except errors.NotFound: @@ -106,12 +106,10 @@ class CSReplicationManager(replication.ReplicationManager): raise errors.NotFound(reason='No agreement found for %s' % hostname) def delete_referral(self, hostname): - esc1_suffix = self.suffix.replace('=', '\\3D').replace(',', '\\2C') - esc2_suffix = self.suffix.replace('=', '%3D').replace(',', '%2C') - dn = 'cn=%s,cn=mapping tree,cn=config' % esc1_suffix + dn = DN(('cn', self.suffix), ('cn', 'mapping tree'), ('cn', 'config')) # TODO: should we detect proto/port somehow ? mod = [(ldap.MOD_DELETE, 'nsslapd-referral', - 'ldap://%s/%s' % (ipautil.format_netloc(hostname, PORT), esc2_suffix))] + 'ldap://%s/%s' % (ipautil.format_netloc(hostname, PORT), self.suffix))] try: self.conn.modify_s(dn, mod) @@ -165,16 +163,16 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose): conn = ipaldap.IPAdmin(host, 636, cacert=CACERT) conn.do_simple_bind(bindpw=dirman_passwd) - dn = str(DN('cn=masters,cn=ipa,cn=etc,%s' % ipautil.realm_to_suffix(realm))) - entries = conn.search_s(dn, ldap.SCOPE_ONELEVEL) + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) + entries = conn.getList(dn, ldap.SCOPE_ONELEVEL) for ent in entries: try: cadn = DN(('cn', 'CA'), DN(ent.dn)) - entry = conn.getEntry(str(cadn), ldap.SCOPE_BASE) - peers[ent.cn] = ['master', ''] + entry = conn.getEntry(cadn, ldap.SCOPE_BASE) + peers[ent.getValue('cn')] = ['master', ''] except errors.NotFound: - peers[ent.cn] = ['CA not configured', ''] + peers[ent.getValue('cn')] = ['CA not configured', ''] except Exception, e: sys.exit("Failed to get data from '%s': %s" % (host, convert_error(e))) @@ -190,13 +188,13 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose): entries = repl.find_replication_agreements() for entry in entries: - print '%s' % entry.nsds5replicahost + print '%s' % entry.getValue('nsds5replicahost') if verbose: - print " last init status: %s" % entry.nsds5replicalastinitstatus - print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastinitend)) - print " last update status: %s" % entry.nsds5replicalastupdatestatus - print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastupdateend)) + print " last init status: %s" % entry.getValue('nsds5replicalastinitstatus') + print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.getValue('nsds5replicalastinitend'))) + print " last update status: %s" % entry.getValue('nsds5replicalastupdatestatus') + print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.getValue('nsds5replicalastupdateend'))) def del_link(realm, replica1, replica2, dirman_passwd, force=False): @@ -316,7 +314,8 @@ def add_link(realm, replica1, replica2, dirman_passwd, options): conn = ipaldap.IPAdmin(replica2, 636, cacert=CACERT) conn.do_simple_bind(bindpw=dirman_passwd) - dn = str(DN('cn=CA,cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (replica2, ipautil.realm_to_suffix(realm)))) + dn = DN(('cn', 'CA'), ('cn', replica2), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), + ipautil.realm_to_suffix(realm)) conn.search_s(dn, ldap.SCOPE_ONELEVEL) conn.unbind_s() except ldap.NO_SUCH_OBJECT: @@ -341,7 +340,7 @@ def add_link(realm, replica1, replica2, dirman_passwd, options): except Exception, e: sys.exit("Failed to get data from '%s': %s" % (replica1, convert_error(e))) - repl1.setup_replication(replica2, PORT, 0, "cn=Directory Manager", dirman_passwd, True, True) + repl1.setup_replication(replica2, PORT, 0, DN(('cn', 'Directory Manager')), dirman_passwd, True, True) print "Connected '%s' to '%s'" % (replica1, replica2) def re_initialize(realm, options): @@ -355,7 +354,7 @@ def re_initialize(realm, options): thishost = installutils.get_fqdn() filter = "(&(nsDS5ReplicaHost=%s)(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement)))" % thishost - entry = repl.conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) + entry = repl.conn.search_s(DN(('cn', 'config')), ldap.SCOPE_SUBTREE, filter) if len(entry) == 0: root_logger.error("Unable to find %s -> %s replication agreement" % (options.fromhost, thishost)) sys.exit(1) diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install index 47bffdf83..d4795f72e 100755 --- a/install/tools/ipa-dns-install +++ b/install/tools/ipa-dns-install @@ -202,7 +202,7 @@ def main(): root_logger.debug("will use dns_forwarders: %s\n", str(dns_forwarders)) if bind.dm_password: - api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", bind_pw=bind.dm_password) + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=bind.dm_password) else: # See if our LDAP server is up and we can talk to it over GSSAPI ccache = krbV.default_context().default_ccache().name diff --git a/install/tools/ipa-managed-entries b/install/tools/ipa-managed-entries index b9a492e48..e68d4d360 100755 --- a/install/tools/ipa-managed-entries +++ b/install/tools/ipa-managed-entries @@ -28,8 +28,8 @@ try: from ipaserver import ipaldap from ipaserver.plugins.ldap2 import ldap2 from ipalib import api, errors - from ipalib.dn import * from ipapython.ipa_log_manager import * + from ipapython.dn import DN except ImportError: print >> sys.stderr, """\ @@ -92,9 +92,8 @@ def main(): ('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), - DN(api.env.basedn) + api.env.basedn ) - managed_entry_definitions_dn = str(managed_entry_definitions_dn) conn = None try: @@ -125,13 +124,13 @@ def main(): # List available Managed Entry Plugins managed_entries = None try: - entries = conn.search_s( + entries = conn.getList( managed_entry_definitions_dn, ldap.SCOPE_SUBTREE, filter ) except Exception, e: root_logger.debug("Search for managed entries failed: %s" % str(e)) sys.exit("Unable to find managed entries at %s" % managed_entry_definitions_dn) - managed_entries = [entry.cn for entry in entries] + managed_entries = [entry.getValue('cn') for entry in entries] if managed_entries: print "Available Managed Entry Definitions:" for managed_entry in managed_entries: @@ -142,22 +141,18 @@ def main(): if not options.managed_entry: sys.exit("\nYou must specify a managed entry definition") else: - rdn = DN( - ('cn', options.managed_entry), - DN(managed_entry_definitions_dn) - ) - def_dn = str(rdn) + def_dn = DN(('cn', options.managed_entry), managed_entry_definitions_dn) disabled = True try: - entries = conn.search_s(def_dn, + entry = conn.getEntry(def_dn, ldap.SCOPE_BASE, filter, ['originfilter'], ) disable_attr = '(objectclass=disable)' try: - org_filter = entries[0].originfilter + org_filter = entry.getValue('originfilter') disabled = re.search(r'%s' % disable_attr, org_filter) except KeyError: sys.exit("%s is not a valid Managed Entry" % def_dn) diff --git a/install/tools/ipa-nis-manage b/install/tools/ipa-nis-manage index 1c6de7b57..5ef3ce0e6 100755 --- a/install/tools/ipa-nis-manage +++ b/install/tools/ipa-nis-manage @@ -30,6 +30,7 @@ try: from ipaserver.plugins.ldap2 import ldap2 from ipalib import api, errors from ipapython.ipa_log_manager import * + from ipapython.dn import DN except ImportError: print >> sys.stderr, """\ There was a problem importing one of the required Python modules. The @@ -39,8 +40,8 @@ error was: """ % sys.exc_value sys.exit(1) -nis_config_dn = "cn=NIS Server,cn=plugins,cn=config" -compat_dn = "cn=Schema Compatibility,cn=plugins,cn=config" +nis_config_dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config')) +compat_dn = DN(('cn', 'Schema Compatibility'), ('cn', 'plugins'), ('cn', 'config')) def parse_options(): usage = "%prog [options] \n" @@ -120,7 +121,7 @@ def main(): try: conn = ldap2(shared_instance=False, base_dn='') conn.connect( - bind_dn='cn=directory manager', bind_pw=dirman_password + bind_dn=DN(('cn', 'directory manager')), bind_pw=dirman_password ) except errors.ExecutionError, lde: sys.exit("An error occurred while connecting to the server: %s" % str(lde)) diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install index c322cb62e..d7baf9c05 100755 --- a/install/tools/ipa-replica-install +++ b/install/tools/ipa-replica-install @@ -36,12 +36,13 @@ from ipaserver.install.installutils import ReplicaConfig, expand_replica_info, r from ipaserver.install.installutils import get_host_name, BadHostError from ipaserver.plugins.ldap2 import ldap2 from ipaserver.install import cainstance -from ipapython import version from ipalib import api, errors, util +from ipapython import version from ipapython.config import IPAOptionParser from ipapython import sysrestore from ipapython import services as ipaservices from ipapython.ipa_log_manager import * +from ipapython.dn import DN log_file_name = "/var/log/ipareplica-install.log" CACERT = "/etc/ipa/ca.crt" @@ -204,7 +205,7 @@ def install_http(config, auto_redirect): return http def install_bind(config, options): - api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=config.dirman_password) if options.forwarders: forwarders = options.forwarders @@ -246,7 +247,7 @@ def install_dns_records(config, options): cur_uri = api.Backend.ldap2.ldap_uri object.__setattr__(api.Backend.ldap2, 'ldap_uri', 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)) - api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=config.dirman_password, tls_cacertfile=CACERT) bind = bindinstance.BindInstance(dm_password=config.dirman_password) @@ -360,10 +361,10 @@ def main(): try: fd = open("/etc/ipa/default.conf", "w") fd.write("[global]\n") - fd.write("host=" + config.host_name + "\n") - fd.write("basedn=" + ipautil.realm_to_suffix(config.realm_name) + "\n") - fd.write("realm=" + config.realm_name + "\n") - fd.write("domain=" + config.domain_name + "\n") + fd.write("host=%s\n" % config.host_name) + fd.write("basedn=%s\n" % str(ipautil.realm_to_suffix(config.realm_name))) + fd.write("realm=%s\n" % config.realm_name) + fd.write("domain=%s\n" % config.domain_name) fd.write("xmlrpc_uri=https://%s/ipa/xml\n" % ipautil.format_netloc(config.host_name)) fd.write("ldap_uri=ldapi://%%2fvar%%2frun%%2fslapd-%s.socket\n" % dsinstance.realm_to_serverid(config.realm_name)) if ipautil.file_exists(config.dir + "/cacert.p12"): @@ -404,14 +405,14 @@ def main(): ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name) try: conn = ldap2(shared_instance=False, ldap_uri=ldapuri, base_dn='') - conn.connect(bind_dn='cn=directory manager', + conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=config.dirman_password, tls_cacertfile=CACERT) replman = ReplicationManager(config.realm_name, config.master_host_name, config.dirman_password) found = False try: - entry = conn.find_entries(u'fqdn=%s' % host, ['dn', 'fqdn'], u'%s,%s' % (api.env.container_host, api.env.basedn)) + entry = conn.find_entries(u'fqdn=%s' % host, ['dn', 'fqdn'], DN(api.env.container_host, api.env.basedn)) print "The host %s already exists on the master server.\nYou should remove it before proceeding:" % host print " %% ipa host-del %s" % host found = True diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage index b095daf03..b2e819d1e 100755 --- a/install/tools/ipa-replica-manage +++ b/install/tools/ipa-replica-manage @@ -29,8 +29,8 @@ from ipaserver.install import bindinstance from ipaserver import ipaldap from ipapython import version from ipalib import api, errors, util -from ipalib.dn import DN from ipapython.ipa_log_manager import * +from ipapython.dn import DN CACERT = "/etc/ipa/ca.crt" @@ -125,23 +125,28 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose): conn.do_simple_bind(bindpw=dirman_passwd) else: conn.do_sasl_gssapi_bind() + except Exception, e: + print "Failed to connect to host '%s': %s" % (host, str(e)) + return - dn = 'cn=masters,cn=ipa,cn=etc,%s' % ipautil.realm_to_suffix(realm) - entries = conn.search_s(dn, ldap.SCOPE_ONELEVEL) - + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) + try: + entries = conn.getList(dn, ldap.SCOPE_ONELEVEL) + except: + print "Failed read master data from '%s': %s" % (host, str(e)) + return + else: for ent in entries: - peers[ent.cn] = ['master', ''] - - dn = 'cn=replicas,cn=ipa,cn=etc,%s' % ipautil.realm_to_suffix(realm) - entries = conn.search_s(dn, ldap.SCOPE_ONELEVEL) + peers[ent.getValue('cn')] = ['master', ''] + dn = DN(('cn', 'replicas'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) + try: + entries = conn.getList(dn, ldap.SCOPE_ONELEVEL) + except: + pass + else: for ent in entries: - peers[ent.cn] = ent.ipaconfigstring.split(':') - - except Exception, e: - print "Failed to get data from '%s': %s" % (host, str(e)) - return - + peers[ent.getValue('cn')] = ent.getValue('ipaConfigString').split(':') if not replica: for k, p in peers.iteritems(): @@ -164,8 +169,8 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose): repl = replication.ReplicationManager(realm, winsync_peer, dirman_passwd) cn, dn = repl.agreement_dn(replica) - entries = repl.conn.search_s(dn, ldap.SCOPE_BASE, - "(objectclass=nsDSWindowsReplicationAgreement)") + entries = repl.conn.getList(dn, ldap.SCOPE_BASE, + "(objectclass=nsDSWindowsReplicationAgreement)") ent_type = 'winsync' else: repl = replication.ReplicationManager(realm, replica, @@ -177,13 +182,13 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose): return for entry in entries: - print '%s: %s' % (entry.nsds5replicahost, ent_type) + print '%s: %s' % (entry.getValue('nsds5replicahost'), ent_type) if verbose: - print " last init status: %s" % entry.nsds5replicalastinitstatus - print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastinitend)) - print " last update status: %s" % entry.nsds5replicalastupdatestatus - print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastupdateend)) + print " last init status: %s" % entry.getValue('nsds5replicalastinitstatus') + print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.getValue('nsds5replicalastinitend'))) + print " last update status: %s" % entry.getValue('nsds5replicalastupdatestatus') + print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.getValue('nsds5replicalastupdateend'))) def del_link(realm, replica1, replica2, dirman_passwd, force=False): @@ -254,9 +259,9 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False): if type1 == replication.WINSYNC: try: - dn = 'cn=%s,cn=replicas,cn=ipa,cn=etc,%s' % (replica2, - ipautil.realm_to_suffix(realm)) - entries = repl1.conn.search_s(dn, ldap.SCOPE_SUBTREE) + dn = DN(('cn', replica2), ('cn', 'replicas'), ('cn', 'ipa'), ('cn', 'etc'), + ipautil.realm_to_suffix(realm)) + entries = repl1.conn.getList(dn, ldap.SCOPE_SUBTREE) if len(entries) != 0: dnset = repl1.conn.get_dns_sorted_by_length(entries, reverse=True) @@ -300,11 +305,11 @@ def del_master(realm, hostname, options): force_del = True if force_del: - dn = 'cn=masters,cn=ipa,cn=etc,%s' % thisrepl.suffix - res = thisrepl.conn.search_s(dn, ldap.SCOPE_ONELEVEL) + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), thisrepl.suffix) + entries = thisrepl.conn.getList(dn, ldap.SCOPE_ONELEVEL) replica_names = [] - for entry in res: - replica_names.append(entry.cn) + for entry in entries: + replica_names.append(entry.getValue('cn')) else: # Get list of agreements. replica_names = delrepl.find_ipa_replication_agreements() @@ -340,7 +345,7 @@ def del_master(realm, hostname, options): if bindinstance.dns_container_exists(options.host, thisrepl.suffix, dm_password=options.dirman_passwd): if options.dirman_passwd: - api.Backend.ldap2.connect(bind_dn='cn=Directory Manager', + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=options.dirman_passwd) else: ccache = krbV.default_context().default_ccache().name @@ -366,7 +371,7 @@ def add_link(realm, replica1, replica2, dirman_passwd, options): repl = replication.ReplicationManager(realm, replica1, dirman_passwd) if repl.get_agreement_type(replica2) == replication.WINSYNC: agreement = repl.get_replication_agreement(replica2) - sys.exit("winsync agreement already exists on subtree %s" % + sys.exit("winsync agreement already exists on subtree %s" % agreement.getValue('nsds7WindowsReplicaSubtree')) else: sys.exit("A replication agreement to %s already exists" % replica2) @@ -407,8 +412,8 @@ def add_link(realm, replica1, replica2, dirman_passwd, options): # from scratch try: masters_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), (api.env.basedn)) - master1_dn = str(DN(('cn', replica1), masters_dn)) - master2_dn = str(DN(('cn', replica2), masters_dn)) + master1_dn = DN(('cn', replica1), masters_dn) + master2_dn = DN(('cn', replica2), masters_dn) repl1.conn.getEntry(master1_dn, ldap.SCOPE_BASE) repl1.conn.getEntry(master2_dn, ldap.SCOPE_BASE) diff --git a/install/tools/ipa-replica-prepare b/install/tools/ipa-replica-prepare index 19360fdf2..210a9de11 100755 --- a/install/tools/ipa-replica-prepare +++ b/install/tools/ipa-replica-prepare @@ -35,6 +35,7 @@ from ipaserver.plugins.ldap2 import ldap2 from ipapython import version from ipapython.config import IPAOptionParser from ipalib import api, errors, util +from ipapython.dn import DN def parse_options(): usage = "%prog [options] FQDN (e.g. replica.example.com)" @@ -99,13 +100,16 @@ def parse_options(): def get_subject_base(host_name, dm_password, suffix): try: conn = ldap2(shared_instance=False, base_dn=suffix) - conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) + conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password) except errors.ExecutionError, e: root_logger.critical("Could not connect to the Directory Server on %s" % host_name) raise e (dn, entry_attrs) = conn.get_ipa_config() conn.disconnect() - return entry_attrs.get('ipacertificatesubjectbase', [None])[0] + subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0] + if subject_base is not None: + subject_base = DN(subject_base) + return subject_base def check_ipa_configuration(realm_name): config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) @@ -200,7 +204,7 @@ def save_config(dir, realm_name, host_name, config.set("realm", "master_host_name", host_name) config.set("realm", "domain_name", domain_name) config.set("realm", "destination_host", dest_host) - config.set("realm", "subject_base", subject_base) + config.set("realm", "subject_base", str(subject_base)) fd = open(dir + "/realm_info", "w") config.write(fd) @@ -260,7 +264,7 @@ def main(): # Try out the password try: conn = ldap2(shared_instance=False) - conn.connect(bind_dn='cn=directory manager', bind_pw=dirman_password) + conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dirman_password) conn.disconnect() except errors.ACIError: sys.exit("\nThe password provided is incorrect for LDAP server %s" % api.env.host) @@ -431,7 +435,7 @@ def main(): if options.ip_address: print "Adding DNS records for %s" % replica_fqdn - api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", bind_pw=dirman_password) + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dirman_password) domain = replica_fqdn.split(".") name = domain.pop(0) diff --git a/install/tools/ipa-server-certinstall b/install/tools/ipa-server-certinstall index 3b19f0452..bc4dde272 100755 --- a/install/tools/ipa-server-certinstall +++ b/install/tools/ipa-server-certinstall @@ -32,6 +32,7 @@ from ipapython.ipautil import user_input from ipaserver.install import certs, dsinstance, httpinstance, installutils from ipalib import api from ipapython.ipa_log_manager import * +from ipapython.dn import DN from ipaserver.plugins.ldap2 import ldap2 def get_realm_name(): @@ -66,9 +67,9 @@ def parse_options(): def set_ds_cert_name(cert_name, dm_password): conn = ldap2(shared_instance=False, base_dn='') - conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) + conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password) mod = {'nssslpersonalityssl': cert_name} - conn.update_entry('cn=RSA,cn=encryption,cn=config', mod) + conn.update_entry(DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')), mod) conn.disconnect() def choose_server_cert(server_certs): diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install index d7de70631..d9682bbb3 100755 --- a/install/tools/ipa-server-install +++ b/install/tools/ipa-server-install @@ -60,11 +60,11 @@ from ipapython.ipautil import * from ipapython import ipautil from ipalib import api, errors, util from ipapython.config import IPAOptionParser -from ipalib.dn import DN from ipalib.x509 import load_certificate_from_file, load_certificate_chain_from_file from ipalib.util import validate_domain_name from ipapython import services as ipaservices from ipapython.ipa_log_manager import * +from ipapython.dn import DN pw_name = None uninstalling = False @@ -530,13 +530,13 @@ def set_subject_in_config(realm_name, dm_password, suffix, subject_base): ) try: conn = ldap2(shared_instance=False, ldap_uri=ldapuri, base_dn=suffix) - conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) + conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password) except errors.ExecutionError, e: root_logger.critical("Could not connect to the Directory Server on %s" % realm_name) raise e (dn, entry_attrs) = conn.get_ipa_config() if 'ipacertificatesubjectbase' not in entry_attrs: - mod = {'ipacertificatesubjectbase': subject_base} + mod = {'ipacertificatesubjectbase': str(subject_base)} conn.update_entry(dn, mod) conn.disconnect() @@ -770,7 +770,7 @@ def main(): realm_name = options.realm_name.upper() if not options.subject: - options.subject = "O=%s" % realm_name + options.subject = DN(('O', realm_name)) if not options.dm_password: dm_password = read_dm_password() @@ -840,10 +840,10 @@ def main(): target_fname = '/etc/ipa/default.conf' fd = open(target_fname, "w") fd.write("[global]\n") - fd.write("host=" + host_name + "\n") - fd.write("basedn=" + ipautil.realm_to_suffix(realm_name) + "\n") - fd.write("realm=" + realm_name + "\n") - fd.write("domain=" + domain_name + "\n") + fd.write("host=%s\n" % host_name) + fd.write("basedn=%s\n" % ipautil.realm_to_suffix(realm_name)) + fd.write("realm=%s\n" % realm_name) + fd.write("domain=%s\n" % domain_name) fd.write("xmlrpc_uri=https://%s/ipa/xml\n" % format_netloc(host_name)) fd.write("ldap_uri=ldapi://%%2fvar%%2frun%%2fslapd-%s.socket\n" % dsinstance.realm_to_serverid(realm_name)) fd.write("enable_ra=True\n") @@ -1046,7 +1046,7 @@ def main(): persistent_search=options.persistent_search, serial_autoincrement=options.serial_autoincrement) if options.setup_dns: - api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", bind_pw=dm_password) + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password) bind.create_instance() print "" diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig index 951bd4854..3041cb60b 100644 --- a/install/tools/ipa-upgradeconfig +++ b/install/tools/ipa-upgradeconfig @@ -253,7 +253,7 @@ def update_dbmodules(realm, filename="/etc/krb5.conf"): newfile.append('#%s' % line) prefix = '' continue - + newfile.append('%s%s' % (prefix, line)) # Append updated dbmodules information diff --git a/install/tools/ipactl b/install/tools/ipactl index 22a4f6e03..e173d10c1 100755 --- a/install/tools/ipactl +++ b/install/tools/ipactl @@ -26,10 +26,10 @@ try: from ipaserver.install.dsinstance import config_dirname, realm_to_serverid from ipaserver.install.installutils import is_ipa_configured, ScriptError from ipapython.ipautil import wait_for_open_ports, wait_for_open_socket + from ipalib import api, errors from ipapython import sysrestore from ipapython import config - from ipalib import api, errors - from ipalib.dn import DN + from ipapython.dn import DN import ldap import ldap.sasl import ldapurl @@ -107,8 +107,7 @@ def emit_err(err): sys.stderr.write(err + '\n') def get_config(dirsrv): - base = "cn=%s,cn=masters,cn=ipa,cn=etc,%s" % (api.env.host, - api.env.basedn) + base = DN(('cn', api.env.host), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) srcfilter = '(ipaConfigString=enabledService)' attrs = ['cn', 'ipaConfigString'] if not dirsrv.is_running(): @@ -127,7 +126,7 @@ def get_config(dirsrv): wait_for_open_ports(host, [int(port)], timeout=api.env.startup_timeout) con = ldap.initialize(api.env.ldap_uri) con.sasl_interactive_bind_s('', SASL_EXTERNAL) - res = con.search_st(base, + res = con.search_st(str(base), ldap.SCOPE_SUBTREE, filterstr=srcfilter, attrlist=attrs, @@ -138,12 +137,10 @@ def get_config(dirsrv): "Directory Server is stopped", 3) except ldap.NO_SUCH_OBJECT: masters_list = [] - dn = str(DN('cn=masters,cn=ipa,cn=etc,%s' % api.env.basedn)) + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) attrs = ['cn'] try: - entries = con.search_s(dn, - ldap.SCOPE_ONELEVEL, - attrlist=attrs,) + entries = con.search_s(str(dn), ldap.SCOPE_ONELEVEL, attrlist=attrs) except Exception, e: masters_list.append("No master found because of error: %s" % str(e)) else: diff --git a/ipa-client/ipa-install/ipa-client-automount b/ipa-client/ipa-install/ipa-client-automount index 713a0e425..d04350ed8 100755 --- a/ipa-client/ipa-install/ipa-client-automount +++ b/ipa-client/ipa-install/ipa-client-automount @@ -31,12 +31,12 @@ import SSSDConfig from optparse import OptionParser from ipalib import api, errors -from ipalib.dn import DN from ipapython import sysrestore from ipapython import ipautil from ipaclient import ipadiscovery from ipaclient import ipachangeconf from ipapython.ipa_log_manager import * +from ipapython.dn import DN from ipapython import services as ipaservices AUTOFS_CONF = '/etc/sysconfig/autofs' diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install index 081ae7f28..3b4d14d48 100755 --- a/ipa-client/ipa-install/ipa-client-install +++ b/ipa-client/ipa-install/ipa-client-install @@ -41,6 +41,7 @@ try: from ipapython import certmonger from ipapython.config import IPAOptionParser from ipalib import api, errors + from ipapython.dn import DN import SSSDConfig from ConfigParser import RawConfigParser from optparse import SUPPRESS_HELP, OptionGroup @@ -510,8 +511,8 @@ def configure_ldap_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, d {'name':'ldap_version', 'type':'option', 'value':'3'}, {'name':'base', 'type':'option', 'value':cli_basedn}, {'name':'empty', 'type':'empty'}, - {'name':'nss_base_passwd', 'type':'option', 'value':'cn=users,cn=accounts,'+cli_basedn+'?sub'}, - {'name':'nss_base_group', 'type':'option', 'value':'cn=groups,cn=accounts,'+cli_basedn+'?sub'}, + {'name':'nss_base_passwd', 'type':'option', 'value':str(DN(('cn', 'users'), ('cn', 'accounts'), cli_basedn))+'?sub'}, + {'name':'nss_base_group', 'type':'option', 'value':str(DN(('cn', 'groups'), ('cn', 'accounts'), cli_basedn))+'?sub'}, {'name':'nss_schema', 'type':'option', 'value':'rfc2307bis'}, {'name':'nss_map_attribute', 'type':'option', 'value':'uniqueMember member'}, {'name':'nss_initgroups_ignoreusers', 'type':'option', 'value':'root,dirsrv'}, @@ -555,8 +556,8 @@ def configure_nslcd_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, {'name':'ldap_version', 'type':'option', 'value':'3'}, {'name':'base', 'type':'option', 'value':cli_basedn}, {'name':'empty', 'type':'empty'}, - {'name':'base passwd', 'type':'option', 'value':'cn=users,cn=accounts,'+cli_basedn}, - {'name':'base group', 'type':'option', 'value':'cn=groups,cn=accounts,'+cli_basedn}, + {'name':'base passwd', 'type':'option', 'value':str(DN(('cn', 'users'), ('cn', 'accounts'), cli_basedn))}, + {'name':'base group', 'type':'option', 'value':str(DN(('cn', 'groups'), ('cn', 'accounts'), cli_basedn))}, {'name':'map group', 'type':'option', 'value':'uniqueMember member'}, {'name':'timelimit', 'type':'option', 'value':'15'}, {'name':'empty', 'type':'empty'}] @@ -743,9 +744,9 @@ def configure_certmonger(fstore, subject_base, cli_realm, hostname, options): # Request our host cert if started: client_nss_nickname = client_nss_nickname_format % hostname - subject = 'CN=%s,%s' % (hostname, subject_base) + subject = DN(('CN', hostname), subject_base) try: - run(["ipa-getcert", "request", "-d", "/etc/pki/nssdb", "-n", client_nss_nickname, "-N", subject, "-K", principal]) + run(["ipa-getcert", "request", "-d", "/etc/pki/nssdb", "-n", client_nss_nickname, "-N", str(subject), "-K", principal]) except: root_logger.error( "%s request for host certificate failed", cmonger.service_name) @@ -1337,7 +1338,7 @@ def install(options, env, fstore, statestore): cli_basedn = ds.basedn cli_basedn_source = ds.basedn_source root_logger.debug("will use discovered basedn: %s", cli_basedn) - subject_base = "O=%s" % cli_realm + subject_base = DN(('O', cli_realm)) # Now do a sanity check on the other servers if options.server and len(options.server) > 1: @@ -1431,7 +1432,7 @@ def install(options, env, fstore, statestore): root_logger.error("Test kerberos configuration failed") return CLIENT_INSTALL_ERROR env['KRB5_CONFIG'] = krb_name - join_args = ["/usr/sbin/ipa-join", "-s", cli_server[0], "-b", realm_to_suffix(cli_realm)] + join_args = ["/usr/sbin/ipa-join", "-s", cli_server[0], "-b", str(realm_to_suffix(cli_realm))] if options.debug: join_args.append("-d") env['XMLRPC_TRACE_CURL'] = 'yes' @@ -1508,6 +1509,7 @@ def install(options, env, fstore, statestore): start = start + 29 subject_base = stderr[start:] subject_base = subject_base.strip() + subject_base = DN(subject_base) finally: if options.principal is not None: diff --git a/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py index ca13d9c19..f91d4075a 100644 --- a/ipa-client/ipaclient/ipadiscovery.py +++ b/ipa-client/ipaclient/ipadiscovery.py @@ -28,7 +28,7 @@ from dns.exception import DNSException from ipapython.ipautil import run, CalledProcessError, valid_ip, get_ipa_basedn, \ realm_to_suffix, format_netloc - +from ipapython.dn import DN NOT_FQDN = -1 NO_LDAP_SERVER = -2 @@ -320,7 +320,7 @@ class IPADiscovery(object): root_logger.debug( "Search for (objectClass=krbRealmContainer) in %s (sub)", self.basedn) - lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") + lret = lh.search_s(str(DN(('cn', 'kerberos'), self.basedn)), ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") if not lret: #something very wrong return [REALM_NOT_FOUND] diff --git a/ipalib/__init__.py b/ipalib/__init__.py index dd861a826..8bf37f048 100644 --- a/ipalib/__init__.py +++ b/ipalib/__init__.py @@ -878,7 +878,7 @@ from backend import Backend from frontend import Command, LocalOrRemote, Updater from frontend import Object, Method, Property from crud import Create, Retrieve, Update, Delete, Search -from parameters import DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str, Password +from parameters import DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str, Password, DNParam from parameters import BytesEnum, StrEnum, AccessTime, File from errors import SkipPluginModule from text import _, ngettext, GettextFactory, NGettextFactory diff --git a/ipalib/aci.py b/ipalib/aci.py index 1b607a393..53c0053c6 100755 --- a/ipalib/aci.py +++ b/ipalib/aci.py @@ -254,32 +254,6 @@ class ACI: # We got this far so lets declare them the same return True -def extract_group_cns(aci_list, client): - """ - Extracts all the cn's from a list of aci's and returns them as a hash - from group_dn to group_cn. - - It first tries to cheat by looking at the first rdn for the - group dn. If that's not cn for some reason, it looks up the group. - """ - group_dn_to_cn = {} - for aci in aci_list: - for dn in (aci.source_group, aci.dest_group): - if not group_dn_to_cn.has_key(dn): - rdn_list = ldap.explode_dn(dn, 0) - first_rdn = rdn_list[0] - (type,value) = first_rdn.split('=') - if type == "cn": - group_dn_to_cn[dn] = value - else: - try: - group = client.get_entry_by_dn(dn, ['cn']) - group_dn_to_cn[dn] = group.getValue('cn') - except Exception: - group_dn_to_cn[dn] = 'unknown' - - return group_dn_to_cn - if __name__ == '__main__': # a = ACI('(targetattr="title")(targetfilter="(memberOf=cn=bar,cn=groups,cn=accounts ,dc=example,dc=com)")(version 3.0;acl "foobar";allow (write) groupdn="ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com";)') # print a diff --git a/ipalib/config.py b/ipalib/config.py index 5e3ef8d9b..3c9aeaa28 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -35,6 +35,7 @@ import os from os import path import sys from socket import getfqdn +from ipapython.dn import DN from base import check_name from constants import CONFIG_SECTION @@ -256,12 +257,14 @@ class Env(object): value = m[value] elif value.isdigit(): value = int(value) + elif key in ('basedn'): + value = DN(value) else: try: value = float(value) except (TypeError, ValueError): pass - assert type(value) in (unicode, int, float, bool, NoneType) + assert type(value) in (unicode, int, float, bool, NoneType, DN) object.__setattr__(self, key, value) self.__d[key] = value diff --git a/ipalib/constants.py b/ipalib/constants.py index f0f89a3b3..59649865b 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -22,6 +22,7 @@ All constants centralised in one file. """ import socket +from ipapython.dn import DN from ipapython.version import VERSION try: FQDN = socket.getfqdn() @@ -69,44 +70,44 @@ DEFAULT_CONFIG = ( # Domain, realm, basedn: ('domain', 'example.com'), ('realm', 'EXAMPLE.COM'), - ('basedn', 'dc=example,dc=com'), + ('basedn', DN(('dc', 'example'), ('dc', 'com'))), # LDAP containers: - ('container_accounts', 'cn=accounts'), - ('container_user', 'cn=users,cn=accounts'), - ('container_group', 'cn=groups,cn=accounts'), - ('container_service', 'cn=services,cn=accounts'), - ('container_host', 'cn=computers,cn=accounts'), - ('container_hostgroup', 'cn=hostgroups,cn=accounts'), - ('container_rolegroup', 'cn=roles,cn=accounts'), - ('container_permission', 'cn=permissions,cn=pbac'), - ('container_privilege', 'cn=privileges,cn=pbac'), - ('container_automount', 'cn=automount'), - ('container_policies', 'cn=policies'), - ('container_configs', 'cn=configs,cn=policies'), - ('container_roles', 'cn=roles,cn=policies'), - ('container_applications', 'cn=applications,cn=configs,cn=policies'), - ('container_policygroups', 'cn=policygroups,cn=configs,cn=policies'), - ('container_policylinks', 'cn=policylinks,cn=configs,cn=policies'), - ('container_netgroup', 'cn=ng,cn=alt'), - ('container_hbac', 'cn=hbac'), - ('container_hbacservice', 'cn=hbacservices,cn=hbac'), - ('container_hbacservicegroup', 'cn=hbacservicegroups,cn=hbac'), - ('container_dns', 'cn=dns'), - ('container_virtual', 'cn=virtual operations,cn=etc'), - ('container_sudorule', 'cn=sudorules,cn=sudo'), - ('container_sudocmd', 'cn=sudocmds,cn=sudo'), - ('container_sudocmdgroup', 'cn=sudocmdgroups,cn=sudo'), - ('container_entitlements', 'cn=entitlements,cn=etc'), - ('container_automember', 'cn=automember,cn=etc'), - ('container_selinux', 'cn=usermap,cn=selinux'), - ('container_s4u2proxy', 'cn=s4u2proxy,cn=etc'), - ('container_cifsdomains', 'cn=ad,cn=etc'), - ('container_trusts', 'cn=trusts'), - ('container_adtrusts', 'cn=ad,cn=trusts'), - ('container_ranges', 'cn=ranges,cn=etc'), - ('container_dna', 'cn=dna,cn=ipa,cn=etc'), - ('container_dna_posix_ids', 'cn=posix-ids,cn=dna,cn=ipa,cn=etc'), + ('container_accounts', DN(('cn', 'accounts'))), + ('container_user', DN(('cn', 'users'), ('cn', 'accounts'))), + ('container_group', DN(('cn', 'groups'), ('cn', 'accounts'))), + ('container_service', DN(('cn', 'services'), ('cn', 'accounts'))), + ('container_host', DN(('cn', 'computers'), ('cn', 'accounts'))), + ('container_hostgroup', DN(('cn', 'hostgroups'), ('cn', 'accounts'))), + ('container_rolegroup', DN(('cn', 'roles'), ('cn', 'accounts'))), + ('container_permission', DN(('cn', 'permissions'), ('cn', 'pbac'))), + ('container_privilege', DN(('cn', 'privileges'), ('cn', 'pbac'))), + ('container_automount', DN(('cn', 'automount'))), + ('container_policies', DN(('cn', 'policies'))), + ('container_configs', DN(('cn', 'configs'), ('cn', 'policies'))), + ('container_roles', DN(('cn', 'roles'), ('cn', 'policies'))), + ('container_applications', DN(('cn', 'applications'), ('cn', 'configs'), ('cn', 'policies'))), + ('container_policygroups', DN(('cn', 'policygroups'), ('cn', 'configs'), ('cn', 'policies'))), + ('container_policylinks', DN(('cn', 'policylinks'), ('cn', 'configs'), ('cn', 'policies'))), + ('container_netgroup', DN(('cn', 'ng'), ('cn', 'alt'))), + ('container_hbac', DN(('cn', 'hbac'))), + ('container_hbacservice', DN(('cn', 'hbacservices'), ('cn', 'hbac'))), + ('container_hbacservicegroup', DN(('cn', 'hbacservicegroups'), ('cn', 'hbac'))), + ('container_dns', DN(('cn', 'dns'))), + ('container_virtual', DN(('cn', 'virtual operations'), ('cn', 'etc'))), + ('container_sudorule', DN(('cn', 'sudorules'), ('cn', 'sudo'))), + ('container_sudocmd', DN(('cn', 'sudocmds'), ('cn', 'sudo'))), + ('container_sudocmdgroup', DN(('cn', 'sudocmdgroups'), ('cn', 'sudo'))), + ('container_entitlements', DN(('cn', 'entitlements'), ('cn', 'etc'))), + ('container_automember', DN(('cn', 'automember'), ('cn', 'etc'))), + ('container_selinux', DN(('cn', 'usermap'), ('cn', 'selinux'))), + ('container_s4u2proxy', DN(('cn', 's4u2proxy'), ('cn', 'etc'))), + ('container_cifsdomains', DN(('cn', 'ad'), ('cn', 'etc'))), + ('container_trusts', DN(('cn', 'trusts'))), + ('container_adtrusts', DN(('cn', 'ad'), ('cn', 'trusts'))), + ('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'))), # Ports, hosts, and URIs: # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri diff --git a/ipalib/dn.py b/ipalib/dn.py deleted file mode 100644 index 6f2f7deb5..000000000 --- a/ipalib/dn.py +++ /dev/null @@ -1,1337 +0,0 @@ -# Authors: -# John Dennis -# -# Copyright (C) 2011 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 . - -from ldap.dn import str2dn, dn2str -from ldap import DECODING_ERROR -import codecs -import sys - -utf8_codec = codecs.lookup('utf-8') - -__all__ = ['AVA', 'RDN', 'DN'] - -''' - -Goal ----- - -To allow a Python programmer the ability to operate on DN's -(Distinguished Names) in a simple intuitive manner supporting all the -Pythonic mechanisms for manipulating objects such that the simple -majority case remains simple with simple code, yet the corner cases -are fully supported. With the result both simple and complex cases are -100% correct. - -This is achieved with a fair of amount of syntax sugar which is best -described as "Do What I Mean" (i.e. DWIM). The class implementations -take simple expressions and internally convert them to their more -complex full definitions hiding much of the complexity from the -programmer. - -Anatomy of a DN ---------------- - -Some definitions: - -AVA - An AVA is an Attribute Value Assertion. In more simple terms it's - an attribute value pair typically expressed as attr=value - (e.g. cn=Bob). Both the attr and value in an AVA when expressed in - a string representation are subject to encoding rules. - -RDN - A RDN is a Relative Distinguished Name. A RDN is a non-empty set of - AVA's. In the common case a RDN is single valued consisting of 1 - AVA (e.g. cn=Bob). But a RDN may be multi-valued consisting of - more than one AVA. Because the RDN is a set of AVA's the AVA's are - unordered when they appear in a multi-valued RDN. In the string - representation of a RDN AVA's are separated by the plus sign (+). - -DN - A DN is a ordered sequence of 1 or more RDN's. In the string - representation of a DN each RDN is separated by a comma (,) - -Thus a DN is: - -Sequence of set of pairs - -The following are valid DN's - -# 1 RDN with 1 AVA (e.g. cn=Bob) -RDN(AVA) - -# 2 RDN's each with 1 AVA (e.g. cn=Bob,dc=redhat.com) -RDN(AVA),RDN(AVA) - -# 2 RDN's the first RDN is multi-valued with 2 AVA's -# the second RDN is singled valued with 1 AVA -# (e.g. cn=Bob+ou=people,dc=redhat.com -RDN({AVA,AVA}),RDN(AVA) - -Common programming mistakes ---------------------------- - -DN's present a pernicious problem for programmers. They appear to have -a very simple string format in the majority case, a sequence of -attr=value pairs separated by commas. For example: - -dn='cn=Bob,ou=people,dc=redhat,dc=com' - -As such there is a tendency to believe you can form DN's by simple -string manipulations such as: - -dn='%s=%s' % ('cn','Bob') + ',ou=people,dc=redhat,dc=com' - -Or to extract a attr & value by searching the string, for example: - -attr=dn[0 : dn.find('=')] -value=dn[dn.find('=')+1 : dn.find(',')] - -Or compare a value returned by an LDAP query to a known value: - -if value == 'Bob' - -All of these simple coding assumptions are WRONG and will FAIL when a -DN is not one of the simple DN's (simple DN's are probably the 95% of -all DN's). This is what makes DN handling pernicious. What works in -95% of the cases and is simple, fails for the 5% of DN's which are not -simple. - -Examples of where the simple assumptions fail are: - -* A RDN may be multi-valued - -* A multi-valued RDN has no ordering on it's components - -* Attr's and values must be UTF-8 encoded - -* String representations of AVA's, RDN's and DN's must be completely UTF-8 - -* An attr or value may have reserved characters which must be escaped. - -* Whitespace needs special handling - -To complicate matters a bit more the RFC for the string representation -of DN's (RFC 4514) permits a variety of different syntax's each of -which can evaluate to exactly the same DN but have different string -representations. For example, the attr "r,w" which contains a reserved -character (the comma) can be encoded as a string in these different -ways: - -'r\,w' # backslash escape -'r\2cw' # hexadecimal ascii escape -'#722C77' # binary encoded - -It should be clear a DN string may NOT be a simple string, rather a DN -string is ENCODED. For simple strings the encoding of the DN is -identical to the simple string value (this common case leads to -erroneous assumptions and bugs because it does not account for -encodings). - -The openldap library we use at the client level uses the backslash -escape form. The LDAP server we use uses the hexadecimal ascii escape -form. Thus 'r,w' appears as 'r\,w' when sent from the client to the -LDAP server as part of a DN. But when it's returned as a DN from the -server in an LDAP search it's returned as 'r\2cw'. Any attempt to -compare 'r\,w' to 'r\2cw' for equality will fail despite the fact they -are indeed equal once decoded. Such a test fails because you're -comparing two different encodings of the same value. In MIME you -wouldn't expect the base64 encoding of a string to be equal to the -same string encoded as quoted-printable would you? - -When you are comparing attrs or values which are part of a DN and -other string you MUST: - -* Know if either of the strings have been encoded and make sure you're - comparing only decoded components component-wise. - -* Extract the component from the DN and decode it. You CANNOT decode - the entire DN as a string and operate on it. Why? Consider a value - with a comma embedded in it. For example: - - cn=r\2cw,cn=privilege - - Is a DN with 2 RDN components: cn=r,w followed by "cn=privilege" - - But if you decode the entire DN string as a whole you would get: - - cn=r,w,cn=privilege - - Which is a malformed DN with 3 RDN's, the 2nd RDN is invalid. - -* Determine if a RDN is multi-valued, if so you must account - for the fact each AVA component in the multi-valued RDN can appear - in any order and still be equivalent. For example the following two - RDN's are equal: - - cn=Bob+ou=people - ou=people+cn=Bob - - In addition each AVA (cn=Bob & ou=people) needs to be - INDEPENDENTLY decoded prior to comparing the unordered set of AVA's - in the multi-valued RDN. - -If you are trying to form a new DN or RDN from a raw string you cannot -simply do string concatenation or string formatting unless you ESCAPE -the components independently prior to concatenation, for example: - - base = 'dc=redhat,dc=com' - value = 'r,w' - dn = 'cn=%s,%s' % (value, base) - -Will result in the malformed DN 'cn=r,w,dc=redhat,dc=com' - -Syntax Sugar ------------- - -The majority of DN's have a simple string form: - -attr=value,attr=value - -We want the programmer to be able to create DN's, compare them, and -operate on their components as simply and concisely as possible so -the classes are implemented to provide a lot of syntax sugar. - -The classes automatically handle UTF-8 <-> Unicode conversions. Every -attr and value which is returned from a class will be Unicode. Every -attr and value assigned into an object will be promoted to -Unicode. All string representations in RFC 4514 format will be UTF-8 -and properly escaped. Thus at the "user" or "API" level every string -is Unicode with the single exception that the str() method returns RFC -compliant escaped UTF-8. - -RDN's are assumed to be single-valued. If you need a multi-valued RDN -(an exception) you must explicitly create a multi-valued RDN. - -Thus DN's are assumed to be a sequence of attr, value pairs, which is -equivalent to a sequence of RDN's. The attr and value in the pair MUST -be strings. - -The DN and RDN constructors take a sequence, the constructor parses -the sequence to find items it knows about. - -The DN constructor will accept in it's sequence: - * tuple of 2 strings, converting it to an RDN - * list of 2 strings, converting it to an RDN - * a RDN object - * a DN syntax string (e.g. 'cn=Bob,dc=redhat.com') - -Note DN syntax strings should be avoided if possible when passing to a -constructor because they run afoul of the problems outlined above -which the DN, RDN & AVA classes are meant to overcome. But sometimes a -DN syntax string is all you have to work with. DN strings which come -from a LDAP library or server will be properly formed and it's safe to -use those. However DN strings provided via user input should be -treated suspiciously as they may be improperly formed. You can test -for this by passing the string to the DN constructor and see if it -throws an exception. - -The sequence passed to the DN constructor takes each item in order, -produces one or more RDN's from it and appends those RDN in order to -its internal RDN sequence. - -For example: - - DN(('cn', 'Bob'), ('dc', 'redhat.com')) - -This is equivalent to the DN string: - - cn=Bob,dc=redhat.com - -And is exactly equal to: - - DN(RDN(AVA('cn','Bob')),RDN(AVA('dc','redhat.com'))) - -The following are alternative syntax's which are all exactly -equivalent to the above example. - - DN(['cn', 'Bob'], ['dc', 'redhat.com']) - DN(RDN('cn', 'Bob'), RDN('dc', 'redhat.com')) - -You can provide a properly escaped string representation. - - DN('cn=Bob,dc=redhat.com') - -You can mix and match any of the forms in the constructor parameter -list. - - DN(('cn', 'Bob'), 'dc=redhat.com') - DN(('cn', 'Bob'), RDN('dc', 'redhat.com')) - -AVA's have an attr and value property, thus if you have an AVA - -# Get the attr and value -ava.attr -> u'cn' -ava.value -> u'Bob' - -# Set the attr and value -ava.attr = 'cn' -ava.value = 'Bob' - -Since RDN's are assumed to be single valued, exactly the same -behavior applies to an RDN. If the RDN is multi-valued then the attr -property returns the attr of the first AVA, likewise for the value. - -# Get the attr and value -rdn.attr -> u'cn' -rdn.value -> u'Bob' - -# Set the attr and value -rdn.attr = 'cn' -rdn.value = 'Bob' - -Also RDN's can be indexed by name or position (see the RDN class doc -for details). - -rdn['cn'] -> u'Bob' -rdn[0] -> AVA('cn', 'Bob') - -A DN is a sequence of RDN's, as such any of Python's container -operators can be applied to a DN in a intuitive way. - -# How many RDN's in a DN? -len(dn) - -# WARNING, this a count of RDN's not how characters there are in the -# string representation the dn, instead that would be: -len(str(dn)) - -# Iterate over each RDN in a DN -for rdn in dn: - -# Get the first RDN in a DN -dn[0] -> RDN('cn', 'Bob') - -# Get the value of the first RDN in a DN -dn[0].value -> u'Bob' - -# Get the value of the first RDN by indexing by attr name -dn['cn'] -> u'Bob' - -# WARNING, when a string is used as an index key the FIRST RDN's value -# in the sequence whose attr matches the key is returned. Thus if you -# have a DN like this "cn=foo,cn=bar" then dn['cn'] will always return -# 'foo' even though there is another attr with the name 'cn'. This is -# almost always what the programmer wants. See the class doc for how -# you can override this default behavior and get a list of every value -# whose attr matches the key. - -# Set the first RDN in the DN (all are equivalent) -dn[0] = ('cn', 'Bob') -dn[0] = ['cn', 'Bob'] -dn[0] = RDN('cn', 'Bob') - -dn[0].attr = 'cn' -dn[0].value = 'Bob' - -# Get the first two RDN's using slices -dn[0:2] - -# Get the last two RDN's using slices -dn[-2:] - -# Get a list of all RDN's using slices -dn[:] - -# Set the 2nd and 3rd RDN using slices (all are equivalent) -dn[1:3] = ('cn', 'Bob), ('dc', 'redhat.com') -dn[1:3] = RDN('cn', 'Bob), RDN('dc', 'redhat.com') - -String representations and escapes: - -# To get an RFC compliant string representation of a DN, RDN or AVA -# simply call str() on it or evaluate it in a string context. -str(dn) -> 'cn=Bob,dc=redhat.com' - -# When working with attr's and values you do not have to worry about -# escapes, simply use the raw unescaped string in a natural fashion. - -rdn = RDN('cn', 'r,w') - -# Thus: -rdn.value == 'r,w' -> True - -# But: -str(rdn) == 'cn=r,w' -> False -# Because: -str(rdn) -> 'cn=r\2cw' or 'cn='r\,w' # depending on the underlying LDAP library - -Equality and Comparing: - -# All DN's, RDN's and AVA's support equality testing in an intuitive -# manner. -dn1 = DN(('cn', 'Bob')) -dn2 = DN(RDN('cn', 'Bob')) -dn1 == dn2 -> True -dn1[0] == dn2[0] -> True -dn1[0].value = 'Bobby' -dn1 == dn2 -> False - -DN objects implement startswith(), endswith() and the "in" membership -operator. You may pass a DN or RDN object to these. Examples: - -if dn.endswith(base_dn): -if dn.startswith(rdn1): -if container_dn in dn: - -# See the class doc for how DN's, RDN's and AVA's compare -# (e.g. cmp()). The general rule is for objects supporting multiple -# values first their lengths are compared, then if the lengths match -# the respective components of each are pair-wise compared until one -# is discovered to be non-equal. The comparision is case insensitive. - -Concatenation and In-Place Addition: - -# DN's and RDN's can be concatenated. -# Return a new DN by appending the RDN's of dn2 to dn1 -dn3 = dn1 + dn2 - -# Append a RDN to DN's RDN sequence (all are equivalent) -dn += ('cn', 'Bob') -dn += RDN('cn', 'Bob') - -# Append a DN to an existing DN -dn1 += dn2 - -Finally see the unittest for a more complete set of ways you can -manipulate these objects. - -''' - -def _adjust_indices(start, end, length): - 'helper to fixup start/end slice values' - - if end > length: - end = length - elif end < 0: - end += length - if end < 0: - end = 0 - - if start < 0: - start += length - if start < 0: - start = 0 - - return start, end - -class AVA(object): - ''' - AVA(arg0, ...) - - An AVA is an LDAP Attribute Value Assertion. It is convenient to think of - AVA's as a pair. AVA's are members of RDN's (Relative - Distinguished Name). - - The AVA constructor is passed a sequence of args and a set of - keyword parameters used for configuration. - - The arg sequence may be: - - 1) With 2 string (or unicode) arguments, the first argument will be the - attr, the 2nd the value. - - 2) With a sigle list or tuple argument containing exactly 2 string (or unicode - members), the first member is the attr and the second is the value. - - 3) With a single string (or unicode) argument, in this case the string will - be interpretted using the DN syntax described in RFC 4514 to yield a AVA - pair. The parsing recognizes the DN syntax escaping rules. - - For example: - - ava = AVA('cn', 'Bob') # case 1: two strings - ava = AVA(('cn', 'Bob')) # case 2: 2-valued tuple - ava = AVA(['cn', 'Bob']) # case 2: 2-valued list - ava = AVA('cn=Bob') # case 3: DN syntax - - AVA object have two properties for accessing their data: - - attr: the attribute name, cn in our exmaple - value: the attribute's value, Bob in our example - - When attr and value are returned they will always be unicode. When - attr or value are set they will be promoted to unicode. - - AVA objects support indexing by name, e.g. - - ava['cn'] - - returns the value (Bob in our example). If the index does key does not match - the attr then a KeyError will be raised. - - AVA objects support equality testing and comparsion (e.g. cmp()). When they - are compared the attr is compared first, if the 2 attr's are equal then the - values are compared. The comparision is case insensitive (because attr's map - to numeric OID's and their values derive from from the 'name' atribute type - (OID 2.5.4.41) whose EQUALITY MATCH RULE is caseIgnoreMatch. - - The str method of an AVA returns the string representation in RFC 4514 DN - syntax with proper escaping. - ''' - flags = 0 - - def __init__(self, *args, **kwds): - if len(args) == 1: - arg = args[0] - if isinstance(arg, basestring): - try: - rdns = str2dn(arg.encode('utf-8')) - except DECODING_ERROR: - raise ValueError("malformed AVA string = \"%s\"" % arg) - if len(rdns) != 1: - raise ValueError("multiple RDN's specified by \"%s\"" % (arg)) - rdn = rdns[0] - if len(rdn) != 1: - raise ValueError("multiple AVA's specified by \"%s\"" % (arg)) - ava = rdn[0] - elif isinstance(arg, (tuple, list)): - ava = arg - if len(ava) != 2: - raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (ava)) - else: - raise TypeError("with 1 argument, argument must be str,unicode,tuple or list, got %s instead" % \ - arg.__class__.__name__) - - attr = ava[0] - value = ava[1] - elif len(args) == 2: - attr = args[0] - value = args[1] - else: - raise TypeError("takes 1 or 2 arguments (%d given)" % (len(args))) - - if not isinstance(attr, basestring): - raise TypeError("attr must be basestring, got %s instead" % attr.__class__.__name__) - if not isinstance(value, basestring): - raise TypeError("value must be basestring, got %s instead" % value.__class__.__name__) - - self.attr = attr - self.value = value - - def _get_attr(self): - return self._attr_unicode - - def _set_attr(self, new_attr): - if not isinstance(new_attr, basestring): - raise TypeError("attr must be basestring, got %s instead" % new_attr.__class__.__name__) - - if isinstance(new_attr, unicode): - self._attr_unicode = new_attr - else: - self._attr_unicode = utf8_codec.decode(new_attr)[0] - - attr = property(_get_attr, _set_attr) - - def _get_value(self): - return self._value_unicode - - def _set_value(self, new_value): - if not isinstance(new_value, basestring): - raise TypeError("value must be basestring, got %s instead" % new_value.__class__.__name__) - - if isinstance(new_value, unicode): - self._value_unicode = new_value - else: - self._value_unicode = utf8_codec.decode(new_value)[0] - - value = property(_get_value, _set_value) - - def _to_openldap(self): - return [[(self._attr_unicode.encode('utf-8'), self._value_unicode.encode('utf-8'), self.flags)]] - - def __str__(self): - return dn2str(self._to_openldap()) - - def __getitem__(self, key): - if isinstance(key, basestring): - if key == self._attr_unicode: - return self._value_unicode - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - raise TypeError("unsupported type for AVA indexing, must be basestring; not %s" % \ - (key.__class__.__name__)) - - def __eq__(self, other): - ''' - The attr comparison is case insensitive because attr is - really an LDAP attribute type which means it's specified with - an OID (dotted number) and not a string. Since OID's are - numeric the human readable name which maps to the OID is not - significant in case. - - The value comparison is also case insensitive because the all - attribute types used in a DN are derived from the 'name' - atribute type (OID 2.5.4.41) whose EQUALITY MATCH RULE is - caseIgnoreMatch. - ''' - if not isinstance(other, self.__class__): - raise TypeError("expected AVA but got %s" % (other.__class__.__name__)) - - return self._attr_unicode.lower() == other.attr.lower() and \ - self._value_unicode.lower() == other.value.lower() - - def __cmp__(self, other): - 'comparision is case insensitive, see __eq__ doc for explanation' - - if not isinstance(other, self.__class__): - raise TypeError("expected AVA but got %s" % (other.__class__.__name__)) - - result = cmp(self._attr_unicode.lower(), other.attr.lower()) - if result != 0: - return result - result = cmp(self._value_unicode.lower(), other.value.lower()) - return result - -class RDN(object): - ''' - RDN(arg0, ..., first_key_match=True) - - An RDN is a LDAP Relative Distinguished Name. RDN's are members of DN's - (Distinguished Name). An RDN contains 1 or more AVA's. If the RDN contains - more than one AVA it is said to be a multi-valued RDN. When an RDN is - multi-valued the AVA's are unorderd comprising a set. However this - implementation orders the AVA's according to the AVA comparison function to - make equality and comparison testing easier. Think of this a canonical - normalization (however LDAP does not impose any ordering on multiple AVA's - within an RDN). Single valued RDN's are the norm and thus the RDN - constructor has simple syntax for them. - - The RDN constructor is passed a sequence of args and a set of - keyword parameters used for configuration. - - The constructor iterates though the sequence and adds AVA's to the RDN. - - The arg sequence may be: - - * A 2-valued tuple or list denotes the pair of an AVA. The - first member is the attr and the second member is the value, both members - must be strings (or unicode). The tuple or list is passed to the AVA - constructor and the resulting AVA is added to the RDN. Multiple tuples or - lists may appear in the argument list, each adds one additional AVA to the - RDN. - - * A single string (or unicode) argument, in this case the string will - be interpretted using the DN syntax described in RFC 4514 to yield one or - more AVA pairs. The parsing recognizes the DN syntax escaping - rules. - - * A AVA object, the AVA will be copied into the new RDN respecting - the constructors keyword configuration parameters. - - * A RDN object, the AVA's in the RDN are copied into the new RDN - respecting the constructors keyword configuration parameters. - - Single AVA Examples: - - RDN(('cn', 'Bob')) # tuple yields 1 AVA - RDN('cn=Bob') # DN syntax with 1 AVA - RDN(AVA('cn', 'Bob')) # AVA object adds 1 AVA - - Multiple AVA Examples: - - RDN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 AVA's - RDN('cn=Bob+ou=people') # DN syntax with 2 AVA's - RDN(AVA('cn', 'Bob'),AVA('ou', 'people')) # 2 AVA objects adds 2 AVA's - RDN(('cn', 'Bob'), 'ou=people') # 2 args, 1st tuple forms 1 AVA, - # 2nd DN syntax string adds 1 AVA, - # 2 AVA's in total - - Note: The RHS of a slice assignment is interpreted exactly in the - same manner as the constructor argument list (see above examples). - - RDN objects support iteration over their AVA members. You can iterate all - AVA members via any Python iteration syntax. RDN objects support full Python - indexing using bracket [] notation. Examples: - - len(rdn) # return the number of AVA's - rdn[0] # indexing the first AVA - rdn['cn'] # index by AVA attr, returns AVA value - for ava in rdn: # iterate over each AVA - rdn[:] # a slice, in this case a copy of each AVA - - WARNING: When indexing by attr (e.g. rdn['cn']) there is a possibility more - than one AVA has the same attr name as the index key. The default behavior - is to return the value of the first AVA whose attr matches the index - key. This behavior can be modified by setting the first_key_match property - to false in the RDN object. If first_key_match is False a list of all values - will be returned instead. The first_key_match behavior is the default and is - useful because duplicate attr names in multi-valued RDN's are rare. We seek - the most useful common case for programmer friendliness, but you should be - aware of the caveat. - - RDN objects support the AVA attr and value properties as another programmer - convenience because the vast majority of RDN's are single valued. The attr - and value properties return the attr and value properties of the first AVA - in the RDN, for example: - - rdn = RDN(('cn', 'Bob')) # rdn has 1 AVA whose attr == 'cn' and value == 'Bob' - len(rdn) -> 1 - rdn.attr -> u'cn' # exactly equivalent to rdn[0].attr - rdn.value -> u'Bob' # exactly equivalent to rdn[0].value - - When attr and value are returned they will always be unicode. When - attr or value are set they will be promoted to unicode. - - If an RDN is multi-valued the attr and value properties still return only - the first AVA's properties, programmer beware! Recall the AVA's in the RDN - are sorted according the to AVA collating semantics. - - RDN objects support equality testing and comparision. See AVA for the - definition of the comparision method. - - RDN objects support concatenation and addition with other RDN's or AVA's - - rdn1 + rdn2 # yields a new RDN object with the contents of each RDN. - rdn1 + ava1 # yields a new RDN object with the contents of rdn1 and ava1 - - RDN objects can add AVA's objects via in-place addition. - - rdn1 += rdn2 # rdn1 now contains the sum of rdn1 and rdn2 - rdn1 += ava1 # rdn1 has ava1 added to it. - - The str method of an RDN returns the string representation in RFC 4514 DN - syntax with proper escaping. - ''' - - flags = 0 - - def __init__(self, *args, **kwds): - self.first_key_match = kwds.get('first_key_match', True) - self.avas = self._avas_from_sequence(args) - self.avas.sort() - - def _ava_from_value(self, value): - if isinstance(value, AVA): - return AVA(value.attr, value.value) - elif isinstance(value, RDN): - avas = [] - for ava in value.avas: - avas.append(AVA(ava.attr, ava.value)) - if len(avas) == 1: - return avas[0] - else: - return avas - elif isinstance(value, basestring): - try: - rdns = str2dn(value.encode('utf-8')) - if len(rdns) != 1: - raise ValueError("multiple RDN's specified by \"%s\"" % (value)) - rdn = rdns[0] - if len(rdn) == 1: - return AVA(rdn[0][0], rdn[0][1]) - else: - avas = [] - for ava_tuple in rdn: - avas.append(AVA(ava_tuple[0], ava_tuple[1])) - return avas - except DECODING_ERROR: - raise ValueError("malformed RDN string = \"%s\"" % value) - elif isinstance(value, (tuple, list)): - if len(value) != 2: - raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (value)) - return AVA(value) - else: - raise TypeError("must be str,unicode,tuple, or AVA, got %s instead" % \ - value.__class__.__name__) - - - def _avas_from_sequence(self, seq): - avas = [] - - for item in seq: - ava = self._ava_from_value(item) - if isinstance(ava, list): - avas.extend(ava) - else: - avas.append(ava) - return avas - - def _to_openldap(self): - return [[(ava.attr.encode('utf-8'), ava.value.encode('utf-8'), self.flags) for ava in self.avas]] - - def __str__(self): - return dn2str(self._to_openldap()) - - def _next(self): - for ava in self.avas: - yield ava - - def __iter__(self): - return self._next() - - def __len__(self): - return len(self.avas) - - def __getitem__(self, key): - if isinstance(key, (int, long, slice)): - return self.avas[key] - elif isinstance(key, basestring): - if self.first_key_match: - for ava in self.avas: - if key == ava.attr: - return ava.value - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - avas = [] - for ava in self.avas: - if key == ava.attr: - avas.append(ava.value) - if len(avas) > 0: - return avas - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - raise TypeError("unsupported type for RDN indexing, must be int, basestring or slice; not %s" % \ - (key.__class__.__name__)) - - def __setitem__(self, key, value): - if isinstance(key, (int, long)): - new_ava = self._ava_from_value(value) - if isinstance(new_ava, list): - raise TypeError("cannot assign multiple AVA's to single entry") - self.avas[key] = new_ava - elif isinstance(key, slice): - avas = self._avas_from_sequence(value) - self.avas[key] = avas - elif isinstance(key, basestring): - new_ava = self._ava_from_value(value) - if isinstance(new_ava, list): - raise TypeError("cannot assign multiple AVA's to single entry") - found = False - i = 0 - while i < len(self.avas): - if key == self.avas[i].attr: - found = True - self.avas[i] = new_ava - if self.first_key_match: - break - i += 1 - if not found: - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - raise TypeError("unsupported type for RDN indexing, must be int, basestring or slice; not %s" % \ - (key.__class__.__name__)) - self.avas.sort() - - def _get_attr(self): - if len(self.avas) == 0: - raise IndexError("No AVA's in this RDN") - return self.avas[0].attr - - def _set_attr(self, new_attr): - if len(self.avas) == 0: - raise IndexError("No AVA's in this RDN") - - if not isinstance(new_attr, basestring): - raise TypeError("attr must be basestring, got %s instead" % new_attr.__class__.__name__) - - self.avas[0].attr = new_attr - - attr = property(_get_attr, _set_attr) - - def _get_value(self): - if len(self.avas) == 0: - raise IndexError("No AVA's in this RDN") - return self.avas[0].value - - def _set_value(self, new_value): - if len(self.avas) == 0: - raise IndexError("No AVA's in this RDN") - - if not isinstance(new_value, basestring): - raise TypeError("value must be basestring, got %s instead" % new_value.__class__.__name__) - - self.avas[0].value = new_value - - value = property(_get_value, _set_value) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("expected RDN but got %s" % (other.__class__.__name__)) - - return self.avas == other.avas - - def __cmp__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("expected RDN but got %s" % (other.__class__.__name__)) - - result = cmp(len(self), len(other)) - if result != 0: - return result - i = 0 - while i < len(self): - result = cmp(self[i], other[i]) - if result != 0: - return result - i += 1 - return 0 - - def __add__(self, other): - result = RDN(self, first_key_match=self.first_key_match) - if isinstance(other, RDN): - for ava in other.avas: - result.avas.append(AVA(ava.attr, ava.value)) - elif isinstance(other, AVA): - result.avas.append(AVA(other.attr, other.value)) - elif isinstance(other, basestring): - rdn = RDN(other) - for ava in rdn.avas: - result.avas.append(AVA(ava.attr, ava.value)) - else: - raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__)) - - result.avas.sort() - return result - - def __iadd__(self, other): - if isinstance(other, RDN): - for ava in other.avas: - self.avas.append(AVA(ava.attr, ava.value)) - elif isinstance(other, AVA): - self.avas.append(AVA(other.attr, other.value)) - elif isinstance(other, basestring): - rdn = RDN(other) - for ava in rdn.avas: - self.avas.append(AVA(ava.attr, ava.value)) - else: - raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__)) - - self.avas.sort() - return self - -class DN(object): - ''' - DN(arg0, ..., first_key_match=True) - - A DN is a LDAP Distinguished Name. A DN is an ordered sequence of RDN's. - - The DN constructor is passed a sequence of args and a set of - keyword parameters used for configuration. normalize means the - attr and value will be converted to lower case. - - The constructor iterates through the sequence and adds the RDN's - it finds in order to the DN object. Each item in the sequence may - be: - - * A 2-valued tuple or list. The first member is the attr and the - second member is the value of an RDN, both members must be - strings (or unicode). The tuple or list is passed to the RDN - constructor and the resulting RDN is appended to the - DN. Multiple tuples or lists may appear in the argument list, - each adds one additional RDN to the DN. - - * A single string (or unicode) argument, in this case the string - will be interpretted using the DN syntax described in RFC 4514 - to yield one or more RDN's which will be appended in order to - the DN. The parsing recognizes the DN syntax escaping rules. - - * A RDN object, the RDN will copied respecting the constructors - keyword configuration parameters and appended in order. - - * A DN object, the RDN's in the DN are copied respecting the - constructors keyword configuration parameters and appended in - order. - - Single DN Examples: - - DN(('cn', 'Bob')) # tuple yields 1 RDN - DN(['cn', 'Bob']) # list yields 1 RDN - DN('cn=Bob') # DN syntax with 1 RDN - DN(RDN('cn', 'Bob')) # RDN object adds 1 RDN - - Multiple RDN Examples: - - DN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 RDN's - # 2 RDN's total - DN('cn=Bob,ou=people') # DN syntax with 2 RDN's - # 2 RDN's total - DN(RDN('cn', 'Bob'),RDN('ou', 'people')) # 2 RDN objects - # 2 RDN's total - DN(('cn', 'Bob'), "ou=people') # 1st tuple adds 1 RDN - # 2nd DN syntax string adds 1 RDN - # 2 RDN's total - base_dn = DN('dc=redhat,dc=com') - container_dn = DN('cn=sudorules,cn=sudo') - DN(('cn', 'Bob'), container_dn, base_dn) - # 1st arg adds 1 RDN, cn=Bob - # 2nd arg adds 2 RDN's, cn=sudorules,cn=sudo - # 3rd arg adds 2 RDN's, dc=redhat,dc=com - # 5 RDN's total - - - Note: The RHS of a slice assignment is interpreted exactly in the - same manner as the constructor argument list (see above examples). - - DN objects support iteration over their RDN members. You can iterate all - RDN members via any Python iteration syntax. DN objects support full Python - indexing using bracket [] notation. Examples: - - len(rdn) # return the number of RDN's - rdn[0] # indexing the first RDN - rdn['cn'] # index by RDN attr, returns RDN value - for ava in rdn: # iterate over each RDN - rdn[:] # a slice, in this case a copy of each RDN - - WARNING: When indexing by attr (e.g. rdn['cn']) there is a possibility more - than one RDN has the same attr name as the index key. The default behavior - is to return the value of the first RDN whose attr matches the index - key. This behavior can be modified by setting the first_key_match property - to false in the RDN object. If first_key_match is False a list of all values - will be returned instead. The first_key_match behavior is the default and is - useful because typical usage is to seek the first matching RDN. We seek - the most useful common case for programmer friendliness, but you should be - aware of the caveat. - - DN object support slices. - - # Get the first two RDN's using slices - dn[0:2] - - # Get the last two RDN's using slices - dn[-2:] - - # Get a list of all RDN's using slices - dn[:] - - # Set the 2nd and 3rd RDN using slices (all are equivalent) - dn[1:3] = ('cn', 'Bob'), ('dc', 'redhat.com') - dn[1:3] = [['cn', 'Bob'], ['dc', 'redhat.com']] - dn[1:3] = RDN('cn', 'Bob'), RDN('dc', 'redhat.com') - - DN objects support the insert operation. - - dn.insert(i,x) is exactly equivalent to dn[i:i] = [x], thus the following - are all equivalent: - - dn.insert(i, ('cn','Bob')) - dn.insert(i, ['cn','Bob']) - dn.insert(i, RDN(('cn','Bob'))) - dn[i:i] = [('cn','Bob')] - - DN objects support equality testing and comparision. See RDN for the - definition of the comparision method. - - DN objects implement startswith(), endswith() and the "in" membership - operator. You may pass a DN or RDN object to these. Examples: - - # Test if dn ends with the contents of base_dn - if dn.endswith(base_dn): - # Test if dn starts with a rdn - if dn.startswith(rdn1): - # Test if a container is present in a dn - if container_dn in dn: - - DN objects support concatenation and addition with other DN's or RDN's - or strings (interpreted as RFC 4514 DN syntax). - - # yields a new DN object with the RDN's of dn2 appended to the RDN's of dn1 - dn1 + dn2 - - # yields a new DN object with the rdn1 appended to the RDN's of dn1 - dn1 + rdn1 - - DN objects can add RDN's objects via in-place addition. - - dn1 += dn2 # dn2 RDN's are appended to the dn1's RDN's - dn1 += rdn1 # dn1 has rdn appended to its RDN's - dn1 += "dc=redhat.com" # string is converted to DN, then appended - - The str method of an DN returns the string representation in RFC 4514 DN - syntax with proper escaping. - ''' - - flags = 0 - - def __init__(self, *args, **kwds): - self.first_key_match = kwds.get('first_key_match', True) - self.first_key_match = True - self.rdns = self._rdns_from_sequence(args) - - def _rdn_from_value(self, value): - if isinstance(value, RDN): - return RDN(value, first_key_match=self.first_key_match) - elif isinstance(value, DN): - rdns = [] - for rdn in value.rdns: - rdns.append(RDN(rdn, first_key_match=self.first_key_match)) - if len(rdns) == 1: - return rdns[0] - else: - return rdns - elif isinstance(value, basestring): - rdns = [] - try: - dn_list = str2dn(value.encode('utf-8')) - for rdn_list in dn_list: - avas = [] - for ava_tuple in rdn_list: - avas.append(AVA(ava_tuple[0], ava_tuple[1])) - rdn = RDN(*avas, first_key_match=self.first_key_match) - rdns.append(rdn) - except DECODING_ERROR: - raise ValueError("malformed RDN string = \"%s\"" % value) - if len(rdns) == 1: - return rdns[0] - else: - return rdns - elif isinstance(value, (tuple, list)): - if len(value) != 2: - raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (value)) - rdn = RDN(value, first_key_match=self.first_key_match) - return rdn - else: - raise TypeError("must be str,unicode,tuple, or RDN, got %s instead" % \ - value.__class__.__name__) - - def _rdns_from_sequence(self, seq): - rdns = [] - - for item in seq: - rdn = self._rdn_from_value(item) - if isinstance(rdn, list): - rdns.extend(rdn) - else: - rdns.append(rdn) - return rdns - - def _to_openldap(self): - return [[(ava.attr.encode('utf-8'), ava.value.encode('utf-8'), self.flags) for ava in rdn] for rdn in self.rdns] - - def __str__(self): - return dn2str(self._to_openldap()) - - def _next(self): - for rdn in self.rdns: - yield rdn - - def __iter__(self): - return self._next() - - def __len__(self): - return len(self.rdns) - - def __getitem__(self, key): - if isinstance(key, (int, long, slice)): - return self.rdns[key] - elif isinstance(key, basestring): - if self.first_key_match: - for rdn in self.rdns: - if key == rdn.attr: - return rdn.value - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - rdns = [] - for rdn in self.rdns: - if key == rdn.attr: - rdns.append(rdn.value) - if len(rdns) > 0: - return rdns - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - raise TypeError("unsupported type for DN indexing, must be int, basestring or slice; not %s" % \ - (key.__class__.__name__)) - - def __setitem__(self, key, value): - if isinstance(key, (int, long)): - new_rdn = self._rdn_from_value(value) - if isinstance(new_rdn, list): - raise TypeError("cannot assign multiple RDN's to single entry") - self.rdns[key] = new_rdn - elif isinstance(key, slice): - rdns = self._rdns_from_sequence(value) - self.rdns[key] = rdns - elif isinstance(key, basestring): - new_rdn = self._rdn_from_value(value) - if isinstance(new_rdn, list): - raise TypeError("cannot assign multiple values to single entry") - found = False - i = 0 - while i < len(self.rdns): - if key == self.rdns[i].attr: - found = True - self.rdns[i] = new_rdn - if self.first_key_match: break - i += 1 - if not found: - raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) - else: - raise TypeError("unsupported type for DN indexing, must be int, basestring or slice; not %s" % \ - (key.__class__.__name__)) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("expected DN but got %s" % (other.__class__.__name__)) - - return self.rdns == other.rdns - - def __cmp__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("expected DN but got %s" % (other.__class__.__name__)) - - result = cmp(len(self), len(other)) - if result != 0: - return result - return self._cmp_sequence(other, 0, len(self)) - - def _cmp_sequence(self, pattern, self_start, pat_len): - self_idx = self_start - pat_idx = 0 - while pat_idx < pat_len: - result = cmp(self[self_idx], pattern[pat_idx]) - if result != 0: - return result - self_idx += 1 - pat_idx += 1 - return 0 - - def __add__(self, other): - result = DN(self, first_key_match=self.first_key_match) - if isinstance(other, self.__class__): - for rdn in other.rdns: - result.rdns.append(RDN(rdn, first_key_match=self.first_key_match)) - elif isinstance(other, RDN): - result.rdns.append(RDN(other, first_key_match=self.first_key_match)) - elif isinstance(other, basestring): - dn = DN(other, first_key_match=self.first_key_match) - for rdn in dn.rdns: - result.rdns.append(rdn) - else: - raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__)) - - return result - - def __iadd__(self, other): - if isinstance(other, DN): - for rdn in other.rdns: - self.rdns.append(RDN(rdn, first_key_match=self.first_key_match)) - elif isinstance(other, RDN): - self.rdns.append(RDN(other, first_key_match=self.first_key_match)) - elif isinstance(other, basestring): - dn = DN(other, first_key_match=self.first_key_match) - self.__iadd__(dn) - else: - raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__)) - - return self - - def insert(self, i, x): - ''' - x must be a 2-value tuple or list promotable to an RDN object, - or a RDN object. - - dn.insert(i, x) is the same as s[i:i] = [x] - - When a negative index is passed as the first parameter to the - insert() method, the list length is added, as for slice - indices. If it is still negative, it is truncated to zero, as - for slice indices. - ''' - self.rdns.insert(i, self._rdn_from_value(x)) - - # The implementation of startswith, endswith, tailmatch, adjust_indices - # was based on the Python's stringobject.c implementation - - def startswith(self, prefix, start=0, end=sys.maxsize): - ''' - Return True if the dn starts with the specified prefix (either a DN or - RDN object), False otherwise. With optional start, test dn beginning at - that position. With optional end, stop comparing dn at that position. - prefix can also be a tuple of dn's or rdn's to try. - ''' - if isinstance(prefix, tuple): - for pat in prefix: - if self._tailmatch(pat, start, end, -1): - return True - return False - - return self._tailmatch(prefix, start, end, -1) - - def endswith(self, suffix, start=0, end=sys.maxsize): - ''' - Return True if dn ends with the specified suffix (either a DN or RDN - object), False otherwise. With optional start, test dn beginning at - that position. With optional end, stop comparing dn at that position. - suffix can also be a tuple of dn's or rdn's to try. - ''' - if isinstance(suffix, tuple): - for pat in suffix: - if self._tailmatch(pat, start, end, +1): - return True - return False - - return self._tailmatch(suffix, start, end, +1) - - def _tailmatch(self, pattern, start, end, direction): - ''' - Matches the end (direction >= 0) or start (direction < 0) of self - against pattern (either a DN or RDN), using the start and end - arguments. Returns 0 if not found and 1 if found. - ''' - - if isinstance(pattern, DN): - pat_len = len(pattern) - elif isinstance(pattern, RDN): - pat_len = 1 - else: - raise TypeError("expected DN or RDN but got %s" % (pattern.__class__.__name__)) - - self_len = len(self) - - start, end = _adjust_indices(start, end, self_len) - - if direction < 0: # starswith - if start+pat_len > self_len: - return 0 - else: # endswith - if end-start < pat_len or start > self_len: - return 0 - - if end-pat_len >= start: - start = end - pat_len - - if isinstance(pattern, DN): - if end-start >= pat_len: - return not self._cmp_sequence(pattern, start, pat_len) - return 0 - else: - return self.rdns[start] == pattern - - def __contains__(self, other): - 'Return the outcome of the test other in self. Note the reversed operands.' - - if isinstance(other, DN): - other_len = len(other) - end = len(self) - other_len - i = 0 - while i <= end: - result = self._cmp_sequence(other, i, other_len) - if result == 0: - return True - i += 1 - return False - - elif isinstance(other, RDN): - return other in self.rdns - else: - raise TypeError("expected DN or RDN but got %s" % (other.__class__.__name__)) - - - - - diff --git a/ipalib/encoder.py b/ipalib/encoder.py deleted file mode 100644 index 691f4d8ca..000000000 --- a/ipalib/encoder.py +++ /dev/null @@ -1,213 +0,0 @@ -# Authors: -# Pavel Zuna -# -# Copyright (C) 2009 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 . -""" -Encoding capabilities. -""" - -from decimal import Decimal - -class EncoderSettings(object): - """ - Container for encoder settings. - """ - encode_to = 'utf-8' - encode_none = False - encode_dict_keys = False - encode_dict_keys_postprocess = True - encode_dict_vals = True - encode_dict_vals_postprocess = True - encode_postprocessor = staticmethod(lambda x: x) - - decode_from = 'utf-8' - decode_none = False - decode_dict_keys = False - decode_dict_keys_postprocess = True - decode_dict_vals = True - decode_dict_vals_postprocess = True - decode_dict_vals_table = dict() - decode_dict_vals_table_keygen = staticmethod(lambda x, y: x) - decode_postprocessor = staticmethod(lambda x: x) - - -class Encoder(object): - """ - Base class implementing encoding of python scalar types to strings - and vise-versa. - """ - - encoder_settings = EncoderSettings() - - def __init__(self): - # each instance should have its own settings - self.encoder_settings = EncoderSettings() - - def _decode_dict_val(self, key, val): - f = self.encoder_settings.decode_dict_vals_table.get( - self.encoder_settings.decode_dict_vals_table_keygen(key, val) - ) - if f: - return val - return self.decode(val) - - def encode(self, var): - """ - Encode any python built-in python type variable into `self.encode_to`. - - Compound types have their individual members encoded. - - Returns an encoded copy of 'var'. - """ - if isinstance(var, str): - return var - elif isinstance(var, unicode): - return self.encoder_settings.encode_postprocessor( - var.encode(self.encoder_settings.encode_to) - ) - elif isinstance(var, bool): - if var: - var = 'TRUE' - else: - var = 'FALSE' - return self.encoder_settings.encode_postprocessor( - var.encode(self.encoder_settings.encode_to) - ) - elif isinstance(var, (float, Decimal, int, long)): - return self.encoder_settings.encode_postprocessor( - str(var).encode(self.encoder_settings.encode_to) - ) - elif isinstance(var, list): - return [self.encode(m) for m in var] - elif isinstance(var, tuple): - return tuple(self.encode(m) for m in var) - elif isinstance(var, dict): - if self.encoder_settings.encode_dict_keys: - dct = dict() - if not self.encoder_settings.encode_dict_keys_postprocess: - tmp = self.encoder_settings.encode_postprocessor - self.encoder_settings.encode_postprocessor = lambda x: x - for (k, v) in var.iteritems(): - dct[self.encode(k)] = v - if not self.encoder_settings.encode_dict_keys_postprocess: - self.encoder_settings.encode_postprocessor = tmp - else: - dct = dict(var) - if self.encoder_settings.encode_dict_vals: - if not self.encoder_settings.encode_dict_vals_postprocess: - tmp = self.encoder_settings.encode_postprocessor - self.encoder_settings.encode_postprocessor = lambda x: x - for (k, v) in dct.iteritems(): - dct[k] = self.encode(v) - if not self.encoder_settings.encode_dict_vals_postprocess: - self.encoder_settings.encode_postprocessor = tmp - return dct - elif var is None: - if self.encoder_settings.encode_none: - return self.encoder_settings.encode_postprocessor( - str(var).encode(self.encoder_settings.encode_to) - ) - return None - raise TypeError('python built-in type expected, got \'%s\'', type(var)) - - def decode(self, var): - """ - Decode strings in `self.decode_from` into python strings. - - Compound types have their individual members decoded. - - Dictionaries can have their values decoded into other types - by looking up keys in `self.decode_dict_vals_table`. - - Returns a decoded copy of 'var'. - """ - if isinstance(var, unicode): - return var - elif isinstance(var, str): - return self.encoder_settings.decode_postprocessor( - var.decode(self.encoder_settings.decode_from) - ) - elif isinstance(var, (bool, float, Decimal, int, long)): - return var - elif isinstance(var, list): - return [self.decode(m) for m in var] - elif isinstance(var, tuple): - return tuple(self.decode(m) for m in var) - elif isinstance(var, dict): - if self.encoder_settings.decode_dict_keys: - dct = dict() - if not self.encoder_settings.decode_dict_keys_postprocess: - tmp = self.encoder_settings.decode_postprocessor - self.encoder_settings.decode_postprocessor = lambda x: x - for (k, v) in var.iteritems(): - dct[self.decode(k)] = v - if not self.encoder_settings.decode_dict_keys_postprocess: - self.encoder_settings.decode_postprocessor = tmp - else: - dct = dict(var) - if self.encoder_settings.decode_dict_vals: - if not self.encoder_settings.decode_dict_vals_postprocess: - tmp = self.encoder_settings.decode_postprocessor - self.encoder_settings.decode_postprocessor = lambda x: x - for (k, v) in dct.iteritems(): - dct[k] = self._decode_dict_val(k, v) - if not self.encoder_settings.decode_dict_vals_postprocess: - self.encoder_settings.decode_postprocessor = tmp - return dct - elif var is None: - if self.encoder_settings.decode_none: - return self.encoder_settings.decode_postprocessor( - str(var).decode(self.encoder_settings.decode_from) - ) - return None - raise TypeError('python built-in type expected, got \'%s\'', type(var)) - -## ENCODER METHOD DECORATORS - -def encode_args(*outer_args): - def decorate(f): - def new_f(*args, **kwargs): - assert isinstance(args[0], Encoder), \ - 'first argument not Encoder instance' - new_args = list(args) - for a in outer_args: - if isinstance(a, int): - if a < len(args): - new_args[a] = args[0].encode(args[a]) - elif isinstance(a, basestring): - if a in kwargs: - kwargs[a] = args[0].encode(kwargs[a]) - else: - raise TypeError( - 'encode_args takes a list of ints and basestrings' - ) - return f(*new_args, **kwargs) - new_f.func_name = f.func_name - return new_f - return decorate - - -def decode_retval(): - def decorate(f): - def new_f(*args, **kwargs): - assert isinstance(args[0], Encoder), \ - 'first argument not Encoder instance' - return args[0].decode(f(*args, **kwargs)) - new_f.func_name = f.func_name - return new_f - return decorate - diff --git a/ipalib/parameters.py b/ipalib/parameters.py index 98b02dd6d..dd505a179 100644 --- a/ipalib/parameters.py +++ b/ipalib/parameters.py @@ -112,7 +112,7 @@ from errors import ConversionError, RequirementError, ValidationError from errors import PasswordMismatch from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR from text import Gettext, FixMe - +from ipapython.dn import DN class DefaultFrom(ReadOnly): """ @@ -1845,6 +1845,23 @@ class AccessTime(Str): return None +class DNParam(Param): + type = DN + + def _convert_scalar(self, value, index=None): + """ + Convert a single scalar value. + """ + if type(value) is self.type: + return value + + try: + dn = DN(value) + except Exception, e: + raise ConversionError(name=self.get_param_name(), index=index, + error=ugettext(e)) + return dn + def create_param(spec): """ Create an `Str` instance from the shorthand ``spec``. diff --git a/ipalib/plugins/aci.py b/ipalib/plugins/aci.py index 7a27ce116..7d5bf504c 100644 --- a/ipalib/plugins/aci.py +++ b/ipalib/plugins/aci.py @@ -123,24 +123,22 @@ from ipalib import api, crud, errors from ipalib import Object, Command from ipalib import Flag, Int, Str, StrEnum from ipalib.aci import ACI -from ipalib.dn import DN from ipalib import output from ipalib import _, ngettext from ipalib.plugins.baseldap import gen_pkey_only_option -if api.env.in_server and api.env.context in ['lite', 'server']: - from ldap import explode_dn from ipapython.ipa_log_manager import * +from ipapython.dn import DN ACI_NAME_PREFIX_SEP = ":" _type_map = { - 'user': 'ldap:///uid=*,%s,%s' % (api.env.container_user, api.env.basedn), - 'group': 'ldap:///cn=*,%s,%s' % (api.env.container_group, api.env.basedn), - 'host': 'ldap:///fqdn=*,%s,%s' % (api.env.container_host, api.env.basedn), - 'hostgroup': 'ldap:///cn=*,%s,%s' % (api.env.container_hostgroup, api.env.basedn), - 'service': 'ldap:///krbprincipalname=*,%s,%s' % (api.env.container_service, api.env.basedn), - 'netgroup': 'ldap:///ipauniqueid=*,%s,%s' % (api.env.container_netgroup, api.env.basedn), - 'dnsrecord': 'ldap:///idnsname=*,%s,%s' % (api.env.container_dns, api.env.basedn), + 'user': 'ldap:///' + str(DN(('uid', '*'), api.env.container_user, api.env.basedn)), + 'group': 'ldap:///' + str(DN(('cn', '*'), api.env.container_group, api.env.basedn)), + 'host': 'ldap:///' + str(DN(('fqdn', '*'), api.env.container_host, api.env.basedn)), + 'hostgroup': 'ldap:///' + str(DN(('cn', '*'), api.env.container_hostgroup, api.env.basedn)), + 'service': 'ldap:///' + str(DN(('krbprincipalname', '*'), api.env.container_service, api.env.basedn)), + 'netgroup': 'ldap:///' + str(DN(('ipauniqueid', '*'), api.env.container_netgroup, api.env.basedn)), + 'dnsrecord': 'ldap:///' + str(DN(('idnsname', '*'), api.env.container_dns, api.env.basedn)), } _valid_permissions_values = [ @@ -247,7 +245,7 @@ def _make_aci(ldap, current, aciname, kw): if 'test' in kw and not kw.get('test'): raise e else: - entry_attrs = {'dn': 'cn=%s,%s' % (kw['permission'], api.env.container_permission)} + entry_attrs = {'dn': DN(('cn', kw['permission']), api.env.container_permission)} elif group: # Not so friendly with groups. This will raise try: @@ -343,10 +341,9 @@ def _aci_to_kw(ldap, a, test=False, pkey_only=False): else: # See if the target is a group. If so we set the # targetgroup attr, otherwise we consider it a subtree - if api.env.container_group in target: - targetdn = unicode(target.replace('ldap:///','')) - target = DN(targetdn) - kw['targetgroup'] = target['cn'] + targetdn = DN(target.replace('ldap:///','')) + if targetdn.endswith(DN(api.env.container_group, api.env.basedn)): + kw['targetgroup'] = targetdn[0]['cn'] else: kw['subtree'] = unicode(target) @@ -357,15 +354,16 @@ def _aci_to_kw(ldap, a, test=False, pkey_only=False): elif groupdn == 'anyone': pass else: - if groupdn.startswith('cn='): - dn = '' + groupdn = DN(groupdn) + if len(groupdn) and groupdn[0].attr == 'cn': + dn = DN() entry_attrs = {} try: (dn, entry_attrs) = ldap.get_entry(groupdn, ['cn']) except errors.NotFound, e: # FIXME, use real name here if test: - dn = 'cn=%s,%s' % ('test', api.env.container_permission) + dn = DN(('cn', 'test'), api.env.container_permission) entry_attrs = {'cn': [u'test']} if api.env.container_permission in dn: kw['permission'] = entry_attrs['cn'][0] @@ -801,11 +799,11 @@ class aci_find(crud.Search): if kw.get('group'): for a in acis: groupdn = a.bindrule['expression'] - groupdn = groupdn.replace('ldap:///','') - cn = None - if groupdn.startswith('cn='): - cn = explode_dn(groupdn)[0] - cn = cn.replace('cn=','') + groupdn = DN(groupdn.replace('ldap:///','')) + try: + cn = groupdn[0]['cn'].value + except (IndexError, KeyError): + cn = None if cn is None or cn != kw['group']: try: results.remove(a) @@ -818,9 +816,11 @@ class aci_find(crud.Search): if 'target' in a.target: target = a.target['target']['expression'] if api.env.container_group in target: - targetdn = unicode(target.replace('ldap:///','')) - cn = explode_dn(targetdn)[0] - cn = cn.replace('cn=','') + targetdn = DN(target.replace('ldap:///','')) + try: + cn = targetdn[0]['cn'] + except (IndexError, KeyError): + cn = None if cn == kw['targetgroup']: found = True if not found: diff --git a/ipalib/plugins/automember.py b/ipalib/plugins/automember.py index 7ccb0bb2f..40359604b 100644 --- a/ipalib/plugins/automember.py +++ b/ipalib/plugins/automember.py @@ -22,8 +22,8 @@ from ipalib import Str, StrEnum from ipalib.plugins.baseldap import * from ipalib import _, ngettext from ipalib.request import context -from ipalib.dn import * import ldap as _ldap +from ipapython.dn import DN __doc__ = _(""" Auto Membership Rule. @@ -200,19 +200,16 @@ class automember(LDAPObject): parent_dn = self.container_dn grouptype = options['type'] try: - ndn = DN(('cn', keys[-1]), ('cn', grouptype), DN(parent_dn)) + ndn = DN(('cn', keys[-1]), ('cn', grouptype), parent_dn) except IndexError: - ndn = DN(('cn', grouptype), DN(parent_dn)) - parent_dn = str(ndn) - return parent_dn + ndn = DN(('cn', grouptype), parent_dn) + return ndn def check_attr(self, attr): """ Verify that the user supplied key is a valid attribute in the schema """ ldap = self.api.Backend.ldap2 - if not ldap.schema: - ldap.get_schema() obj = ldap.schema.get_obj(_ldap.schema.AttributeType, attr) if obj is not None: return obj @@ -238,6 +235,7 @@ class automember_add(LDAPCreate): msg_summary = _('Added automember rule "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) entry_attrs['cn'] = keys[-1] if not automember_container_exists(self.api.Backend.ldap2): @@ -284,6 +282,7 @@ class automember_add_condition(LDAPUpdate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # Check to see if the automember rule exists try: (tdn, test_attrs) = ldap.get_entry(dn, []) @@ -370,6 +369,7 @@ class automember_remove_condition(LDAPUpdate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # Check to see if the automember rule exists try: (tdn, test_attrs) = ldap.get_entry(dn, []) @@ -479,10 +479,10 @@ class automember_find(LDAPSearch): ) def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) scope = ldap.SCOPE_SUBTREE - ndn = DN(('cn', options['type']), DN(base_dn)) - base_dn = str(ndn) - return (filters, base_dn, scope) + ndn = DN(('cn', options['type']), base_dn) + return (filters, ndn, scope) api.register(automember_find) @@ -520,7 +520,6 @@ class automember_default_group_set(LDAPUpdate): def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): dn = DN(('cn', options['type']), api.env.container_automember) - dn = str(dn) entry_attrs['automemberdefaultgroup'] = self.obj.dn_exists(options['type'], options['automemberdefaultgroup']) return dn @@ -542,7 +541,6 @@ class automember_default_group_remove(LDAPUpdate): def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): dn = DN(('cn', options['type']), api.env.container_automember) - dn = str(dn) attr = 'automemberdefaultgroup' (dn, entry_attrs_) = ldap.get_entry( @@ -556,6 +554,7 @@ class automember_default_group_remove(LDAPUpdate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if 'automemberdefaultgroup' not in entry_attrs: entry_attrs['automemberdefaultgroup'] = unicode(_('No default (fallback) group set')) return dn @@ -576,10 +575,10 @@ class automember_default_group_show(LDAPRetrieve): def pre_callback(self, ldap, dn, attrs_list, *keys, **options): dn = DN(('cn', options['type']), api.env.container_automember) - dn = str(dn) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if 'automemberdefaultgroup' not in entry_attrs: entry_attrs['automemberdefaultgroup'] = unicode(_('No default (fallback) group set')) return dn diff --git a/ipalib/plugins/automount.py b/ipalib/plugins/automount.py index 5c9f42b4c..8e9eb5745 100644 --- a/ipalib/plugins/automount.py +++ b/ipalib/plugins/automount.py @@ -227,6 +227,7 @@ class automountlocation_add(LDAPCreate): msg_summary = _('Added automount location "%(value)s"') def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) # create auto.master for the new location self.api.Command['automountmap_add'](keys[-1], u'auto.master') @@ -595,6 +596,7 @@ class automountmap_del(LDAPDelete): msg_summary = _('Deleted automount map "%(value)s"') def post_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) # delete optional parental connection (direct maps may not have this) try: (dn_, entry_attrs) = ldap.find_entry_by_attr( @@ -718,7 +720,7 @@ class automountkey(LDAPObject): (kwargs['automountkey'], kwargs['automountinformation']) else: sfilter = '(automountkey=%s)' % kwargs['automountkey'] - basedn = 'automountmapname=%s,cn=%s,%s' % (parent_keys[1], parent_keys[0], self.container_dn) + basedn = DN(('automountmapname', parent_keys[1]), ('cn', parent_keys[0]), self.container_dn) attrs_list = ['*'] (entries, truncated) = ldap.find_entries(sfilter, attrs_list, basedn, _ldap.SCOPE_ONELEVEL) @@ -790,6 +792,7 @@ class automountkey_add(LDAPCreate): internal_options = ['description', 'add_operation'] def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) options.pop('add_operation', None) options.pop('description', None) self.obj.check_key_uniqueness(keys[-2], keys[-1], **options) @@ -926,6 +929,7 @@ class automountkey_mod(LDAPUpdate): yield key def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if 'newautomountkey' in options: entry_attrs['automountkey'] = options['newautomountkey'] if 'newautomountinformation' in options: diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 32dae5160..e05f59ff4 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -34,7 +34,7 @@ from ipalib.cli import to_cli, from_cli from ipalib import output from ipalib.text import _ from ipalib.util import json_serialize, validate_hostname -from ipalib.dn import * +from ipapython.dn import DN, RDN global_output_params = ( Flag('has_password', @@ -200,6 +200,7 @@ def validate_attribute(ugettext, name, attr): raise errors.ValidationError(name=name, error='Invalid format. Should be name=value') def get_effective_rights(ldap, dn, attrs=None): + assert isinstance(dn, DN) if attrs is None: attrs = ['*', 'nsaccountlock', 'cospriority'] rights = ldap.get_effective_rights(dn, attrs) @@ -332,6 +333,7 @@ def add_external_pre_callback(membertype, ldap, dn, keys, options): membertype is the type of member """ + assert isinstance(dn, DN) # validate hostname with allowed underscore characters, non-fqdn # hostnames are allowed def validate_host(hostname): @@ -361,12 +363,14 @@ def add_external_post_callback(memberattr, membertype, externalattr, ldap, compl membertype is the type of member: user, externalattr is one of externaluser, """ + assert isinstance(dn, DN) completed_external = 0 normalize = options.get('external_callback_normalize', True) # Sift through the failures. We assume that these are all # entries that aren't stored in IPA, aka external entries. if memberattr in failed and membertype in failed[memberattr]: (dn, entry_attrs_) = ldap.get_entry(dn, [externalattr]) + assert isinstance(dn, DN) members = entry_attrs.get(memberattr, []) external_entries = entry_attrs_.get(externalattr, []) lc_external_entries = set(e.lower() for e in external_entries) @@ -374,6 +378,7 @@ def add_external_post_callback(memberattr, membertype, externalattr, ldap, compl for entry in failed[memberattr][membertype]: membername = entry[0].lower() member_dn = api.Object[membertype].get_dn(membername) + assert isinstance(member_dn, DN) if (membername not in lc_external_entries and member_dn not in members): # Not an IPA entry, assume external @@ -406,6 +411,7 @@ def add_external_post_callback(memberattr, membertype, externalattr, ldap, compl return (completed + completed_external, dn) def remove_external_post_callback(memberattr, membertype, externalattr, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) # Run through the failures and gracefully remove any member defined # as an external member. if memberattr in failed and membertype in failed[memberattr]: @@ -440,7 +446,7 @@ def host_is_master(ldap, fqdn): Raises an exception if a master, otherwise returns nothing. """ - master_dn = str(DN('cn=%s' % fqdn, 'cn=masters,cn=ipa,cn=etc', api.env.basedn)) + master_dn = DN(('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) try: (dn, entry_attrs) = ldap.get_entry(master_dn, ['objectclass']) raise errors.ValidationError(name='hostname', error=_('An IPA master host cannot be deleted or disabled')) @@ -518,9 +524,11 @@ class LDAPObject(Object): return self.backend.make_dn_from_attr( self.primary_key.name, keys[-1], parent_dn ) + assert isinstance(parent_dn, DN) return parent_dn def get_primary_key_from_dn(self, dn): + assert isinstance(dn, DN) try: if self.rdn_attribute: (dn, entry_attrs) = self.backend.get_entry( @@ -532,8 +540,6 @@ class LDAPObject(Object): return '' except errors.NotFound: pass - # DN object assures we're returning a decoded (unescaped) value - dn = DN(dn) try: return dn[self.primary_key.name] except KeyError: @@ -567,7 +573,7 @@ class LDAPObject(Object): for member in entry_attrs.setdefault(attr, []): for ldap_obj_name in self.attribute_members[attr]: ldap_obj = self.api.Object[ldap_obj_name] - if member.find(ldap_obj.container_dn) > 0: + if ldap_obj.container_dn in member: new_attr = '%s_%s' % (attr, ldap_obj.name) entry_attrs.setdefault(new_attr, []).append( ldap_obj.get_primary_key_from_dn(member) @@ -623,7 +629,6 @@ class LDAPObject(Object): def __json__(self): ldap = self.backend - ldap.get_schema() json_dict = dict( (a, getattr(self, a)) for a in self.json_friendly_attributes ) @@ -777,52 +782,47 @@ last, after all sets and adds."""), _callback_registry = dict(pre={}, post={}, exc={}, interactive_prompt={}) - def _convert_2_dict(self, attrs): + def _convert_2_dict(self, ldap, attrs): """ Convert a string in the form of name/value pairs into a dictionary. :param attrs: A list of name/value pair strings, in the "name=value" format. May also be a single string, or None. """ - if attrs is None: - return {} - - if not isinstance(attrs, (tuple, list)): - attrs = [attrs] newdict = {} + if attrs is None: + attrs = [] + elif not type(attrs) in (list, tuple): + attrs = [attrs] for a in attrs: - m = re.match("^\s*(?P.*?)\s*=\s*(?P.*?)\s*$", a) - attr = str(m.group('attr').lower()) - value = m.group('value') - + m = re.match("\s*(.*?)\s*=\s*(.*?)\s*$", a) + attr = str(m.group(1)).lower() + value = m.group(2) if attr in self.obj.params and attr not in self.params: # The attribute is managed by IPA, but it didn't get cloned # to the command. This happens with no_update/no_create attrs. raise errors.ValidationError( name=attr, error=_('attribute is not configurable')) + if len(value) == 0: + # None means "delete this attribute" + value = None - newdict.setdefault(attr, []).append(value) + if ldap.has_dn_syntax(attr): + value = DN(value) + if attr in newdict: + if type(value) in (tuple,): + newdict[attr] += list(value) + else: + newdict[attr].append(value) + else: + if type(value) in (tuple,): + newdict[attr] = list(value) + else: + newdict[attr] = [value] return newdict - def _convert_entry(self, entry_attrs): - result = {} - for attr, val in entry_attrs.iteritems(): - if val is None: - val = [] - elif not isinstance(val, (tuple, list)): - val = [val] - - result[attr] = [] - for v in val: - if isinstance(v, str): - # This is a Binary value, base64 encode it - v = base64.b64encode(v) - result[attr].append(unicode(v)) - - return result - def process_attr_options(self, entry_attrs, dn, keys, options): """ Process all --setattr, --addattr, and --delattr options and add the @@ -847,14 +847,15 @@ last, after all sets and adds."""), :param keys: List of command arguments :param options: List of options """ + if all(k not in options for k in ("setattr", "addattr", "delattr")): return ldap = self.obj.backend - adddict = self._convert_2_dict(options.get('addattr', [])) - setdict = self._convert_2_dict(options.get('setattr', [])) - deldict = self._convert_2_dict(options.get('delattr', [])) + adddict = self._convert_2_dict(ldap, options.get('addattr', [])) + setdict = self._convert_2_dict(ldap, options.get('setattr', [])) + deldict = self._convert_2_dict(ldap, options.get('delattr', [])) setattrs = set(setdict.keys()) addattrs = set(adddict.keys()) @@ -865,24 +866,24 @@ last, after all sets and adds."""), direct_del = delattrs needldapattrs = [] else: + assert isinstance(dn, DN) direct_add = setattrs & addattrs direct_del = setattrs & delattrs needldapattrs = list((addattrs | delattrs) - setattrs) - mod_attrs = self._convert_entry(entry_attrs) - for attr, val in setdict.iteritems(): - mod_attrs[attr] = val + entry_attrs[attr] = val for attr in direct_add: - mod_attrs.setdefault(attr, []).extend(adddict[attr]) + entry_attrs.setdefault(attr, []).extend(adddict[attr]) for attr in direct_del: for delval in deldict[attr]: try: - mod_attrs[attr].remove(delval) + entry_attrs[attr].remove(delval) except ValueError: - raise errors.AttrValueNotFound(attr=attr, value=delval) + raise errors.AttrValueNotFound(attr=attr, + value=delval) if needldapattrs: try: @@ -901,27 +902,28 @@ last, after all sets and adds."""), raise errors.ValidationError(name=del_nonexisting.pop(), error=_('No such attribute on this entry')) - old_entry = self._convert_entry(old_entry) - for attr in needldapattrs: - mod_attrs[attr] = old_entry.get(attr, []) + entry_attrs[attr] = old_entry.get(attr, []) if attr in addattrs: - mod_attrs[attr].extend(adddict.get(attr, [])) + entry_attrs[attr].extend(adddict.get(attr, [])) for delval in deldict.get(attr, []): try: - mod_attrs[attr].remove(delval) + entry_attrs[attr].remove(delval) except ValueError: + if isinstance(delval, str): + # This is a Binary value, base64 encode it + delval = unicode(base64.b64encode(delval)) raise errors.AttrValueNotFound(attr=attr, value=delval) # normalize all values changedattrs = setattrs | addattrs | delattrs for attr in changedattrs: - value = mod_attrs[attr] if attr in self.params and self.params[attr].attribute: - param = self.params[attr] # convert single-value params to scalars + param = self.params[attr] + value = entry_attrs[attr] if not param.multivalue: if len(value) == 1: value = value[0] @@ -931,19 +933,19 @@ last, after all sets and adds."""), raise errors.OnlyOneValueAllowed(attr=attr) # validate, convert and encode params try: - value = param(value) + value = param(value) except errors.ValidationError, err: raise errors.ValidationError(name=attr, error=err.error) except errors.ConversionError, err: raise errors.ConversionError(name=attr, error=err.error) + entry_attrs[attr] = value else: # unknown attribute: remove duplicite and invalid values - value = list(set([val for val in value if val])) - if not value: - value = None - elif isinstance(value, (tuple, list)) and len(value) == 1: - value = value[0] - entry_attrs[attr] = value + entry_attrs[attr] = list(set([val for val in entry_attrs[attr] if val])) + if not entry_attrs[attr]: + entry_attrs[attr] = None + elif isinstance(entry_attrs[attr], (tuple, list)) and len(entry_attrs[attr]) == 1: + entry_attrs[attr] = entry_attrs[attr][0] @classmethod def register_pre_callback(cls, callback, first=False): @@ -1021,8 +1023,13 @@ class LDAPCreate(BaseLDAPCommand, crud.Create): entry_attrs[self.obj.uuid_attribute] = 'autogenerate' dn = self.obj.get_dn(*keys, **options) + assert isinstance(dn, DN) if self.obj.rdn_attribute: - if not dn.startswith('%s=' % self.obj.primary_key.name): + try: + dn_attr = dn[0].attr + except (IndexError, KeyError): + dn_attr = None + if dn_attr != self.obj.primary_key.name: self.obj.handle_duplicate_entry(*keys) dn = ldap.make_dn( entry_attrs, self.obj.rdn_attribute, self.obj.container_dn @@ -1038,9 +1045,9 @@ class LDAPCreate(BaseLDAPCommand, crud.Create): for callback in self.get_callbacks('pre'): dn = callback( self, ldap, dn, entry_attrs, attrs_list, *keys, **options) + assert isinstance(dn, DN) _check_single_value_attrs(self.params, entry_attrs) - ldap.get_schema() _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.limit_object_classes), entry_attrs.keys(), allow_only=True) _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.disallow_object_classes), entry_attrs.keys(), allow_only=False) @@ -1074,16 +1081,19 @@ class LDAPCreate(BaseLDAPCommand, crud.Create): self.obj.primary_key.name, keys[-1], object_class, attrs_list, self.obj.container_dn ) + assert isinstance(dn, DN) else: (dn, entry_attrs) = self._exc_wrapper(keys, options, ldap.get_entry)( dn, attrs_list, normalize=self.obj.normalize_dn ) + assert isinstance(dn, DN) except errors.NotFound: self.obj.handle_not_found(*keys) for callback in self.get_callbacks('post'): dn = callback(self, ldap, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) entry_attrs['dn'] = dn self.obj.convert_attribute_members(entry_attrs, *keys, **options) @@ -1092,9 +1102,11 @@ class LDAPCreate(BaseLDAPCommand, crud.Create): return dict(result=entry_attrs, value=u'') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return dn def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -1181,6 +1193,7 @@ class LDAPRetrieve(LDAPQuery): ldap = self.obj.backend dn = self.obj.get_dn(*keys, **options) + assert isinstance(dn, DN) if options.get('all', False): attrs_list = ['*'] + self.obj.default_attributes @@ -1189,11 +1202,13 @@ class LDAPRetrieve(LDAPQuery): for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, attrs_list, *keys, **options) + assert isinstance(dn, DN) try: (dn, entry_attrs) = self._exc_wrapper(keys, options, ldap.get_entry)( dn, attrs_list, normalize=self.obj.normalize_dn ) + assert isinstance(dn, DN) except errors.NotFound: self.obj.handle_not_found(*keys) @@ -1202,17 +1217,21 @@ class LDAPRetrieve(LDAPQuery): for callback in self.get_callbacks('post'): dn = callback(self, ldap, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) self.obj.convert_attribute_members(entry_attrs, *keys, **options) + assert isinstance(dn, DN) entry_attrs['dn'] = dn if self.obj.primary_key and keys[-1] is not None: return dict(result=entry_attrs, value=keys[-1]) return dict(result=entry_attrs, value=u'') def pre_callback(self, ldap, dn, attrs_list, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return dn def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -1261,6 +1280,7 @@ class LDAPUpdate(LDAPQuery, crud.Update): raise errors.EmptyModlist() dn = self.obj.get_dn(*keys, **options) + assert isinstance(dn, DN) entry_attrs = self.args_options_2_entry(**options) @@ -1279,8 +1299,8 @@ class LDAPUpdate(LDAPQuery, crud.Update): for callback in self.get_callbacks('pre'): dn = callback( self, ldap, dn, entry_attrs, attrs_list, *keys, **options) + assert isinstance(dn, DN) - ldap.get_schema() _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.limit_object_classes), entry_attrs.keys(), allow_only=True) _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.disallow_object_classes), entry_attrs.keys(), allow_only=False) @@ -1294,10 +1314,10 @@ class LDAPUpdate(LDAPQuery, crud.Update): if self.obj.rdn_is_primary_key and self.obj.primary_key.name in entry_attrs: # RDN change self._exc_wrapper(keys, options, ldap.update_entry_rdn)(dn, - unicode('%s=%s' % (self.obj.primary_key.name, - entry_attrs[self.obj.primary_key.name]))) + RDN((self.obj.primary_key.name, entry_attrs[self.obj.primary_key.name]))) rdnkeys = keys[:-1] + (entry_attrs[self.obj.primary_key.name], ) dn = self.obj.get_dn(*rdnkeys) + assert isinstance(dn, DN) del entry_attrs[self.obj.primary_key.name] options['rdnupdate'] = True rdnupdate = True @@ -1306,6 +1326,7 @@ class LDAPUpdate(LDAPQuery, crud.Update): # to decide what to do. An EmptyModlist in this context doesn't # mean an error occurred, just that there were no other updates to # perform. + assert isinstance(dn, DN) self._exc_wrapper(keys, options, ldap.update_entry)(dn, entry_attrs, normalize=self.obj.normalize_dn) except errors.EmptyModlist, e: if not rdnupdate: @@ -1327,6 +1348,7 @@ class LDAPUpdate(LDAPQuery, crud.Update): for callback in self.get_callbacks('post'): dn = callback(self, ldap, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) self.obj.convert_attribute_members(entry_attrs, *keys, **options) if self.obj.primary_key and keys[-1] is not None: @@ -1334,9 +1356,11 @@ class LDAPUpdate(LDAPQuery, crud.Update): return dict(result=entry_attrs, value=u'') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return dn def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -1360,11 +1384,14 @@ class LDAPDelete(LDAPMultiQuery): def delete_entry(pkey): nkeys = keys[:-1] + (pkey, ) dn = self.obj.get_dn(*nkeys, **options) + assert isinstance(dn, DN) for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, *nkeys, **options) + assert isinstance(dn, DN) def delete_subtree(base_dn): + assert isinstance(base_dn, DN) truncated = True while truncated: try: @@ -1412,9 +1439,11 @@ class LDAPDelete(LDAPMultiQuery): return dict(result=dict(failed=u''), value=u'') def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) return True def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -1495,14 +1524,17 @@ class LDAPAddMember(LDAPModMember): (member_dns, failed) = self.get_member_dns(**options) dn = self.obj.get_dn(*keys, **options) + assert isinstance(dn, DN) for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, member_dns, failed, *keys, **options) + assert isinstance(dn, DN) completed = 0 for (attr, objs) in member_dns.iteritems(): for ldap_obj_name in objs: for m_dn in member_dns[attr][ldap_obj_name]: + assert isinstance(m_dn, DN) if not m_dn: continue try: @@ -1534,7 +1566,9 @@ class LDAPAddMember(LDAPModMember): (completed, dn) = callback( self, ldap, completed, failed, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) + assert isinstance(dn, DN) entry_attrs['dn'] = dn self.obj.convert_attribute_members(entry_attrs, *keys, **options) return dict( @@ -1544,9 +1578,11 @@ class LDAPAddMember(LDAPModMember): ) def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return (completed, dn) def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -1583,14 +1619,17 @@ class LDAPRemoveMember(LDAPModMember): (member_dns, failed) = self.get_member_dns(**options) dn = self.obj.get_dn(*keys, **options) + assert isinstance(dn, DN) for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, member_dns, failed, *keys, **options) + assert isinstance(dn, DN) completed = 0 for (attr, objs) in member_dns.iteritems(): for ldap_obj_name, m_dns in objs.iteritems(): for m_dn in m_dns: + assert isinstance(m_dn, DN) if not m_dn: continue try: @@ -1625,7 +1664,9 @@ class LDAPRemoveMember(LDAPModMember): (completed, dn) = callback( self, ldap, completed, failed, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) + assert isinstance(dn, DN) entry_attrs['dn'] = dn self.obj.convert_attribute_members(entry_attrs, *keys, **options) @@ -1636,9 +1677,11 @@ class LDAPRemoveMember(LDAPModMember): ) def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return (completed, dn) def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -1765,6 +1808,7 @@ class LDAPSearch(BaseLDAPCommand, crud.Search): base_dn = self.api.Object[self.obj.parent_object].get_dn(*args[:-1]) else: base_dn = self.obj.container_dn + assert isinstance(base_dn, DN) search_kw = self.args_options_2_entry(**options) @@ -1812,6 +1856,7 @@ class LDAPSearch(BaseLDAPCommand, crud.Search): for callback in self.get_callbacks('pre'): (filter, base_dn, scope) = callback( self, ldap, filter, attrs_list, base_dn, scope, *args, **options) + assert isinstance(base_dn, DN) try: (entries, truncated) = self._exc_wrapper(args, options, ldap.find_entries)( @@ -1827,14 +1872,16 @@ class LDAPSearch(BaseLDAPCommand, crud.Search): if self.sort_result_entries: if self.obj.primary_key: - sortfn=lambda x,y: cmp(x[1][self.obj.primary_key.name][0].lower(), y[1][self.obj.primary_key.name][0].lower()) - entries.sort(sortfn) + def sort_key(x): + return x[1][self.obj.primary_key.name][0].lower() + entries.sort(key=sort_key) if not options.get('raw', False): for e in entries: self.obj.convert_attribute_members(e[1], *args, **options) for e in entries: + assert isinstance(e[0], DN) e[1]['dn'] = e[0] entries = [e for (dn, e) in entries] @@ -1845,6 +1892,7 @@ class LDAPSearch(BaseLDAPCommand, crud.Search): ) def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) return (filters, base_dn, scope) def post_callback(self, ldap, entries, truncated, *args, **options): @@ -1926,9 +1974,11 @@ class LDAPAddReverseMember(LDAPModReverseMember): # Ensure our target exists result = self.api.Command[self.show_command](keys[-1])['result'] dn = result['dn'] + assert isinstance(dn, DN) for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, *keys, **options) + assert isinstance(dn, DN) if options.get('all', False): attrs_list = ['*'] + self.obj.default_attributes @@ -1967,7 +2017,9 @@ class LDAPAddReverseMember(LDAPModReverseMember): (completed, dn) = callback( self, ldap, completed, failed, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) + assert isinstance(dn, DN) entry_attrs['dn'] = dn return dict( completed=completed, @@ -1976,9 +2028,11 @@ class LDAPAddReverseMember(LDAPModReverseMember): ) def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return (completed, dn) def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): @@ -2022,9 +2076,11 @@ class LDAPRemoveReverseMember(LDAPModReverseMember): # Ensure our target exists result = self.api.Command[self.show_command](keys[-1])['result'] dn = result['dn'] + assert isinstance(dn, DN) for callback in self.get_callbacks('pre'): dn = callback(self, ldap, dn, *keys, **options) + assert isinstance(dn, DN) if options.get('all', False): attrs_list = ['*'] + self.obj.default_attributes @@ -2063,7 +2119,9 @@ class LDAPRemoveReverseMember(LDAPModReverseMember): (completed, dn) = callback( self, ldap, completed, failed, dn, entry_attrs, *keys, **options) + assert isinstance(dn, DN) + assert isinstance(dn, DN) entry_attrs['dn'] = dn return dict( completed=completed, @@ -2072,9 +2130,11 @@ class LDAPRemoveReverseMember(LDAPModReverseMember): ) def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) return dn def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return (completed, dn) def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py index 9573bbb65..ef0fd79fa 100644 --- a/ipalib/plugins/config.py +++ b/ipalib/plugins/config.py @@ -19,7 +19,7 @@ # along with this program. If not, see . from ipalib import api -from ipalib import Bool, Int, Str, IA5Str, StrEnum +from ipalib import Bool, Int, Str, IA5Str, StrEnum, DNParam from ipalib.plugins.baseldap import * from ipalib import _ from ipalib.errors import ValidationError @@ -149,7 +149,7 @@ class config(LDAPObject): label=_('Enable migration mode'), doc=_('Enable migration mode'), ), - Str('ipacertificatesubjectbase', + DNParam('ipacertificatesubjectbase', cli_name='subject', label=_('Certificate Subject base'), doc=_('Base for certificate subjects (OU=Test,O=Example)'), @@ -199,7 +199,7 @@ class config(LDAPObject): ) def get_dn(self, *keys, **kwargs): - return 'cn=ipaconfig,cn=etc' + return DN(('cn', 'ipaconfig'), ('cn', 'etc')) api.register(config) @@ -208,6 +208,7 @@ class config_mod(LDAPUpdate): __doc__ = _('Modify configuration options.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if 'ipadefaultprimarygroup' in entry_attrs: group=entry_attrs['ipadefaultprimarygroup'] try: diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index 6727d052f..991eb0164 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -35,7 +35,6 @@ from ipalib.util import (validate_zonemgr, normalize_zonemgr, validate_hostname, validate_dns_label, validate_domain_name, get_dns_forward_zone_update_policy, get_dns_reverse_zone_update_policy) from ipapython.ipautil import valid_ip, CheckedIPAddress, is_host_resolvable -from ldap import explode_dn __doc__ = _(""" Domain Name System (DNS) @@ -802,10 +801,10 @@ class DNSRecord(Str): # callbacks for per-type special record behavior def dnsrecord_add_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): - pass + assert isinstance(dn, DN) def dnsrecord_add_post_callback(self, ldap, dn, entry_attrs, *keys, **options): - pass + assert isinstance(dn, DN) class ForwardRecord(DNSRecord): extra = ( @@ -817,6 +816,7 @@ class ForwardRecord(DNSRecord): ) def dnsrecord_add_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) reverse_option = self._convert_dnsrecord_extra(self.extra[0]) if options.get(reverse_option.name): records = entry_attrs.get(self.name, []) @@ -832,6 +832,7 @@ class ForwardRecord(DNSRecord): setattr(context, '%s_reverse' % self.name, entry_attrs.get(self.name)) def dnsrecord_add_post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) rev_records = getattr(context, '%s_reverse' % self.name, []) if rev_records: @@ -1727,6 +1728,7 @@ class dnszone_add(LDAPCreate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if not dns_container_exists(self.api.Backend.ldap2): raise errors.NotFound(reason=_('DNS is not configured')) @@ -1751,6 +1753,7 @@ class dnszone_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if 'ip_address' in options: nameserver = entry_attrs['idnssoamname'][0][:-1] # ends with a dot nsparts = nameserver.split('.') @@ -1813,6 +1816,7 @@ class dnszone_find(LDAPSearch): ) def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) if options.get('forward_only', False): search_kw = {} search_kw['idnsname'] = _valid_reverse_zones.keys() @@ -1980,6 +1984,7 @@ class dnsrecord(LDAPObject): ) def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) nsrecords = entry_attrs.get('nsrecord') if options.get('force', False) or nsrecords is None: return @@ -1987,6 +1992,7 @@ class dnsrecord(LDAPObject): check_ns_rec_resolvable(keys[0], nsrecord) def _ptrrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) ptrrecords = entry_attrs.get('ptrrecord') if ptrrecords is None: return @@ -2015,6 +2021,7 @@ class dnsrecord(LDAPObject): % dict(name=zone_name, count=zone_len, user_count=ip_addr_comp_count))) def run_precallback_validators(self, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) ldap = self.api.Backend.ldap2 for rtype in entry_attrs: @@ -2049,7 +2056,7 @@ class dnsrecord(LDAPObject): def get_dns_masters(self): ldap = self.api.Backend.ldap2 - base_dn = 'cn=masters,cn=ipa,cn=etc,%s' % self.api.env.basedn + base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), self.api.env.basedn) ldap_filter = '(&(objectClass=ipaConfigObject)(cn=DNS))' dns_masters = [] @@ -2058,9 +2065,12 @@ class dnsrecord(LDAPObject): for entry in entries: master_dn = entry[0] - if master_dn.startswith('cn='): - master = explode_dn(master_dn)[1].replace('cn=','') + assert isinstance(master_dn, DN) + try: + master = master_dn[1]['cn'] dns_masters.append(master) + except (IndexError, KeyError): + pass except errors.NotFound: return [] @@ -2254,6 +2264,7 @@ class dnsrecord_add(LDAPCreate): kw.update(user_options) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) precallback_attrs = [] processed_attrs = [] for option in options: @@ -2354,6 +2365,7 @@ class dnsrecord_add(LDAPCreate): raise exc def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) for attr in getattr(context, 'dnsrecord_precallback_attrs', []): param = self.params[attr] param.dnsrecord_add_post_callback(ldap, dn, entry_attrs, *keys, **options) @@ -2382,6 +2394,7 @@ class dnsrecord_mod(LDAPUpdate): return super(dnsrecord_mod, self).args_options_2_entry(*keys, **options) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if options.get('rename') and self.obj.is_pkey_zone_record(*keys): # zone rename is not allowed raise errors.ValidationError(name='rename', @@ -2466,10 +2479,12 @@ class dnsrecord_mod(LDAPUpdate): return result def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if self.obj.is_pkey_zone_record(*keys): entry_attrs[self.obj.primary_key.name] = [_dns_zone_record] self.obj.postprocess_record(entry_attrs, **options) + return dn def interactive_prompt_callback(self, kw): try: @@ -2564,6 +2579,7 @@ class dnsrecord_del(LDAPUpdate): yield option def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) try: (dn_, old_entry) = ldap.get_entry( dn, _record_attributes, @@ -2626,9 +2642,11 @@ class dnsrecord_del(LDAPUpdate): return result def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if self.obj.is_pkey_zone_record(*keys): entry_attrs[self.obj.primary_key.name] = [_dns_zone_record] self.obj.postprocess_record(entry_attrs, **options) + return dn def args_options_2_entry(self, *keys, **options): self.obj.has_cli_options(options, self.no_option_msg) @@ -2697,6 +2715,7 @@ class dnsrecord_show(LDAPRetrieve): ) def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if self.obj.is_pkey_zone_record(*keys): entry_attrs[self.obj.primary_key.name] = [_dns_zone_record] self.obj.postprocess_record(entry_attrs, **options) @@ -2723,6 +2742,7 @@ class dnsrecord_find(LDAPSearch): yield option def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) # include zone record (root entry) in the search return (filter, base_dn, ldap.SCOPE_SUBTREE) @@ -2774,7 +2794,7 @@ class dns_is_enabled(Command): NO_CLI = True has_output = output.standard_value - base_dn = 'cn=masters,cn=ipa,cn=etc,%s' % api.env.basedn + base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) filter = '(&(objectClass=ipaConfigObject)(cn=DNS))' def execute(self, *args, **options): diff --git a/ipalib/plugins/entitle.py b/ipalib/plugins/entitle.py index 5ccdb6510..67e7f959e 100644 --- a/ipalib/plugins/entitle.py +++ b/ipalib/plugins/entitle.py @@ -318,6 +318,7 @@ class entitle_consume(LDAPUpdate): return result def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) quantity = keys[-1] os.environ['LANG'] = 'en_US' @@ -361,6 +362,7 @@ class entitle_consume(LDAPUpdate): Returning the certificates isn't very interesting. Return the status of entitlements instead. """ + assert isinstance(dn, DN) if 'usercertificate' in entry_attrs: del entry_attrs['usercertificate'] if 'userpkcs12' in entry_attrs: @@ -504,7 +506,7 @@ class entitle_register(LDAPCreate): """ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): - dn = '%s,%s' % (self.obj.container_dn, self.api.env.basedn) + dn = DN(self.obj.container_dn, self.api.env.basedn) if not ldap.can_add(dn): raise errors.ACIError(info='No permission to register') os.environ['LANG'] = 'en_US' @@ -604,6 +606,7 @@ class entitle_import(LDAPUpdate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) try: (db, uuid, certfile, keyfile) = get_uuid(ldap) if db is not None: @@ -622,7 +625,7 @@ class entitle_import(LDAPUpdate): raise errors.CertificateFormatError(error=_('Not an entitlement certificate')) except M2Crypto.X509.X509Error: raise errors.CertificateFormatError(error=_('Not an entitlement certificate')) - dn = 'ipaentitlementid=%s,%s' % (entry_attrs['ipaentitlementid'], dn) + dn = DN(('ipaentitlementid', entry_attrs['ipaentitlementid']), dn) (dn, current_attrs) = ldap.get_entry( dn, ['*'], normalize=self.obj.normalize_dn ) @@ -689,6 +692,7 @@ class entitle_sync(LDAPUpdate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) os.environ['LANG'] = 'en_US' locale.setlocale(locale.LC_ALL, '') @@ -719,6 +723,7 @@ class entitle_sync(LDAPUpdate): Returning the certificates isn't very interesting. Return the status of entitlements instead. """ + assert isinstance(dn, DN) if 'usercertificate' in entry_attrs: del entry_attrs['usercertificate'] if 'userpkcs12' in entry_attrs: diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py index 74bea1700..011587206 100644 --- a/ipalib/plugins/group.py +++ b/ipalib/plugins/group.py @@ -156,6 +156,7 @@ class group_add(LDAPCreate): # As both 'external' and 'nonposix' options have default= set for # them, they will always be present in options dict, thus we can # safely reference the values + assert isinstance(dn, DN) if options['external']: entry_attrs['objectclass'].append('ipaexternalgroup') if 'gidnumber' in options: @@ -176,6 +177,7 @@ class group_del(LDAPDelete): msg_summary = _('Deleted group "%(value)s"') def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) config = ldap.get_ipa_config()[1] def_primary_group = config.get('ipadefaultprimarygroup', '') def_primary_group_dn = group_dn = self.obj.get_dn(def_primary_group) @@ -192,6 +194,7 @@ class group_del(LDAPDelete): return dn def post_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) try: api.Command['pwpolicy_del'](keys[-1]) except errors.NotFound: @@ -220,6 +223,7 @@ class group_mod(LDAPUpdate): ) def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if ('posix' in options and options['posix']) or 'gidnumber' in options: (dn, old_entry_attrs) = ldap.get_entry(dn, ['objectclass']) if 'ipaexternalgroup' in old_entry_attrs['objectclass']: @@ -275,6 +279,7 @@ class group_find(LDAPSearch): ) def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) # if looking for private groups, we need to create a new search filter, # because private groups have different object classes if options['private']: @@ -319,6 +324,7 @@ class group_add_member(LDAPAddMember): ) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) result = (completed, dn) if 'ipaexternalmember' in options: if not _dcerpc_bindings_installed: @@ -367,6 +373,7 @@ class group_remove_member(LDAPRemoveMember): ) def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) if keys[0] == protected_group_name: result = api.Command.group_show(protected_group_name) users_left = set(result['result'].get('member_user', [])) @@ -377,6 +384,7 @@ class group_remove_member(LDAPRemoveMember): return dn def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) result = (completed, dn) if 'ipaexternalmember' in options: sids = options['ipaexternalmember'] diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py index 460083622..94f0d0e20 100644 --- a/ipalib/plugins/hbacrule.py +++ b/ipalib/plugins/hbacrule.py @@ -231,6 +231,7 @@ class hbacrule_add(LDAPCreate): msg_summary = _('Added HBAC rule "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # HBAC rules are enabled by default entry_attrs['ipaenabledflag'] = 'TRUE' return dn @@ -244,6 +245,7 @@ class hbacrule_del(LDAPDelete): msg_summary = _('Deleted HBAC rule "%(value)s"') def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) kw = dict(seealso=keys[0]) _entries = api.Command.selinuxusermap_find(None, **kw) if _entries['count']: @@ -260,6 +262,7 @@ class hbacrule_mod(LDAPUpdate): msg_summary = _('Modified HBAC rule "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, attrs_list) except errors.NotFound: @@ -436,6 +439,7 @@ class hbacrule_add_user(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -464,6 +468,7 @@ class hbacrule_add_host(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -492,6 +497,7 @@ class hbacrule_add_sourcehost(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -502,6 +508,7 @@ class hbacrule_add_sourcehost(LDAPAddMember): return add_external_pre_callback('host', ldap, dn, keys, options) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return add_external_post_callback('sourcehost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(hbacrule_add_sourcehost) @@ -514,6 +521,7 @@ class hbacrule_remove_sourcehost(LDAPRemoveMember): member_count_out = ('%i object removed.', '%i objects removed.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return remove_external_post_callback('sourcehost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(hbacrule_remove_sourcehost) @@ -526,6 +534,7 @@ class hbacrule_add_service(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index e8967b8c0..a417ba0f7 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -38,10 +38,10 @@ from ipalib.plugins.dns import (dns_container_exists, _record_types, from ipalib.plugins.dns import get_reverse_zone from ipalib import _, ngettext from ipalib import x509 -from ipalib.dn import * from ipalib.request import context from ipalib.util import validate_sshpubkey, output_sshpubkey from ipapython.ipautil import ipa_generate_password, CheckedIPAddress, make_sshfp +from ipapython.dn import DN __doc__ = _(""" Hosts/Machines @@ -379,6 +379,7 @@ class host_add(LDAPCreate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if options.get('ip_address') and dns_container_exists(ldap): parts = keys[-1].split('.') host = parts[0] @@ -423,6 +424,7 @@ class host_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) exc = None if dns_container_exists(ldap): try: @@ -483,6 +485,7 @@ class host_del(LDAPDelete): ) def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) # If we aren't given a fqdn, find it if _hostname_validator(None, keys[-1]) is not None: hostentry = api.Command['host_show'](keys[-1])['result'] @@ -599,6 +602,7 @@ class host_mod(LDAPUpdate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # Allow an existing OTP to be reset but don't allow a OTP to be # added to an enrolled host. if options.get('userpassword') or options.get('random'): @@ -690,6 +694,7 @@ class host_mod(LDAPUpdate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if options.get('random', False): entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword')) set_certificate_attrs(entry_attrs) @@ -728,6 +733,7 @@ class host_find(LDAPSearch): yield option def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) if 'locality' in attrs_list: attrs_list.remove('locality') attrs_list.append('l') @@ -808,6 +814,7 @@ class host_show(LDAPRetrieve): member_attributes = ['managedby'] def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.get_password_attributes(ldap, dn, entry_attrs) if entry_attrs['has_password']: # If an OTP is set there is no keytab, at least not one @@ -891,8 +898,7 @@ class host_disable(LDAPQuery): try: serial = unicode(x509.get_serial_number(cert, x509.DER)) try: - result = api.Command['cert_show'](unicode(serial))['result' -] + result = api.Command['cert_show'](unicode(serial))['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke'](unicode(serial), revocation_reason=4) @@ -928,6 +934,7 @@ class host_disable(LDAPQuery): ) def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(entry_attrs) return dn @@ -941,6 +948,7 @@ class host_add_managedby(LDAPAddMember): allow_same = True def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(entry_attrs) return (completed, dn) @@ -954,6 +962,7 @@ class host_remove_managedby(LDAPRemoveMember): has_output_params = LDAPRemoveMember.has_output_params + host_output_params def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(entry_attrs) return (completed, dn) diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py index b68c45842..7e6fe6d65 100644 --- a/ipalib/plugins/hostgroup.py +++ b/ipalib/plugins/hostgroup.py @@ -21,7 +21,7 @@ from ipalib.plugins.baseldap import * from ipalib import api, Int, _, ngettext, errors from ipalib.plugins.netgroup import NETGROUP_PATTERN, NETGROUP_PATTERN_ERRMSG -from ipalib.dn import DN +from ipapython.dn import DN __doc__ = _(""" Groups of hosts. @@ -119,6 +119,7 @@ class hostgroup_add(LDAPCreate): msg_summary = _('Added hostgroup "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) try: # check duplicity with hostgroups first to provide proper error netgroup = api.Command['hostgroup_show'](keys[-1]) @@ -140,6 +141,7 @@ class hostgroup_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) # Always wait for the associated netgroup to be created so we can # be sure to ignore it in memberOf newentry = wait_for_value(ldap, dn, 'objectclass', 'mepOriginEntry') @@ -166,6 +168,7 @@ class hostgroup_mod(LDAPUpdate): msg_summary = _('Modified hostgroup "%(value)s"') def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(dn, entry_attrs) return dn @@ -195,6 +198,7 @@ class hostgroup_show(LDAPRetrieve): __doc__ = _('Display information about a hostgroup.') def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof( dn, entry_attrs) return dn @@ -205,6 +209,7 @@ class hostgroup_add_member(LDAPAddMember): __doc__ = _('Add members to a hostgroup.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(dn, entry_attrs) return (completed, dn) @@ -215,6 +220,7 @@ class hostgroup_remove_member(LDAPRemoveMember): __doc__ = _('Remove members from a hostgroup.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(dn, entry_attrs) return (completed, dn) diff --git a/ipalib/plugins/krbtpolicy.py b/ipalib/plugins/krbtpolicy.py index 0f80c770c..60674cc06 100644 --- a/ipalib/plugins/krbtpolicy.py +++ b/ipalib/plugins/krbtpolicy.py @@ -71,7 +71,7 @@ class krbtpolicy(LDAPObject): """ Kerberos Ticket Policy object """ - container_dn = 'cn=%s,cn=kerberos' % api.env.realm + container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos')) object_name = _('kerberos ticket policy settings') default_attributes = ['krbmaxticketlife', 'krbmaxrenewableage'] limit_object_classes = ['krbticketpolicyaux'] @@ -112,6 +112,7 @@ class krbtpolicy_mod(LDAPUpdate): __doc__ = _('Modify Kerberos ticket policy.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # disable all flag # ticket policies are attached to objects with unrelated attributes if options.get('all'): @@ -125,6 +126,7 @@ class krbtpolicy_show(LDAPRetrieve): __doc__ = _('Display the current Kerberos ticket policy.') def pre_callback(self, ldap, dn, attrs_list, *keys, **options): + assert isinstance(dn, DN) # disable all flag # ticket policies are attached to objects with unrelated attributes if options.get('all'): @@ -132,6 +134,7 @@ class krbtpolicy_show(LDAPRetrieve): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if keys[-1] is not None: # if policy for a specific user isn't set, display global values if 'krbmaxticketlife' not in entry_attrs or \ diff --git a/ipalib/plugins/migration.py b/ipalib/plugins/migration.py index a8c1c6d82..157ab4447 100644 --- a/ipalib/plugins/migration.py +++ b/ipalib/plugins/migration.py @@ -21,10 +21,8 @@ import re import ldap as _ldap from ipalib import api, errors, output -from ipalib import Command, Password, Str, Flag, StrEnum +from ipalib import Command, Password, Str, Flag, StrEnum, DNParam from ipalib.cli import to_cli -from ipalib.util import validate_dn_param -from ipalib.dn import * from ipalib.plugins.user import NO_UPG_MAGIC if api.env.in_server and api.env.context in ['lite', 'server']: try: @@ -32,6 +30,7 @@ if api.env.in_server and api.env.context in ['lite', 'server']: except StandardError, e: raise e from ipalib import _ +from ipapython.dn import DN __doc__ = _(""" Migration to IPA @@ -114,18 +113,10 @@ _dn_err_msg = _('Malformed DN') _supported_schemas = (u'RFC2307bis', u'RFC2307') -_compat_dn = "cn=Schema Compatibility,cn=plugins,cn=config" - -def is_DN_syntax(ldap, attr): - """ - Check the schema to see if the attribute uses DN syntax. - - Returns True/False - """ - obj = ldap.schema.get_obj(_ldap.schema.AttributeType, attr) - return obj is not None and obj.syntax == '1.3.6.1.4.1.1466.115.121.1.12' +_compat_dn = DN(('cn', 'Schema Compatibility'), ('cn', 'plugins'), ('cn', 'config')) def _pre_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwargs): + assert isinstance(dn, DN) attr_blacklist = ['krbprincipalkey','memberofindirect','memberindirect'] attr_blacklist.extend(kwargs.get('attr_blacklist', [])) ds_ldap = ctx['ds_ldap'] @@ -198,17 +189,18 @@ def _pre_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwargs # tree for attr in entry_attrs.keys(): - if is_DN_syntax(ldap, attr): + if ldap.has_dn_syntax(attr): for ind, value in enumerate(entry_attrs[attr]): + assert isinstance(value, DN) try: (remote_dn, remote_entry) = ds_ldap.get_entry(value, [api.Object.user.primary_key.name, api.Object.group.primary_key.name]) except errors.NotFound: api.log.warn('%s: attribute %s refers to non-existent entry %s' % (pkey, attr, value)) continue - if value.lower().endswith(search_bases['user']): + if value.endswith(search_bases['user']): primary_key = api.Object.user.primary_key.name container = api.env.container_user - elif value.lower().endswith(search_bases['group']): + elif value.endswith(search_bases['group']): primary_key = api.Object.group.primary_key.name container = api.env.container_group else: @@ -221,15 +213,13 @@ def _pre_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwargs api.log.debug('converting DN value %s for %s in %s' % (value, attr, dn)) rdnval = remote_entry[primary_key][0].lower() - entry_attrs[attr][ind] = \ - str(DN((primary_key, rdnval), - container, - api.env.basedn)) + entry_attrs[attr][ind] = DN((primary_key, rdnval), container, api.env.basedn) return dn def _post_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx): + assert isinstance(dn, DN) # add user to the default group try: ldap.add_entry_to_group(dn, ctx['def_group_dn']) @@ -246,6 +236,7 @@ def _post_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx): # GROUP MIGRATION CALLBACKS AND VARS def _pre_migrate_group(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwargs): + def convert_members_rfc2307bis(member_attr, search_bases, overwrite=False): """ Convert DNs in member attributes to work in IPA. @@ -253,24 +244,19 @@ def _pre_migrate_group(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwarg new_members = [] entry_attrs.setdefault(member_attr, []) for m in entry_attrs[member_attr]: + assert isinstance(m, DN) try: - # what str2dn returns looks like [[('cn', 'foo', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)]] - rdn = _ldap.dn.str2dn(m ,flags=_ldap.DN_FORMAT_LDAPV3)[0] - rdnval = rdn[0][1] + rdnval = m[0].value except IndexError: api.log.error('Malformed DN %s has no RDN?' % m) continue - if m.lower().endswith(search_bases['user']): + if m.endswith(search_bases['user']): api.log.info('migrating user %s' % m) - m = '%s=%s,%s' % (api.Object.user.primary_key.name, - rdnval, - api.env.container_user) - elif m.lower().endswith(search_bases['group']): + m = DN((api.Object.user.primary_key.name, rdnval), api.env.container_user) + elif m.endswith(search_bases['group']): api.log.info('migrating group %s' % m) - m = '%s=%s,%s' % (api.Object.group.primary_key.name, - rdnval, - api.env.container_group) + m = DN((api.Object.group.primary_key.name, rdnval), api.env.container_group) else: api.log.error('entry %s does not belong into any known container' % m) continue @@ -290,12 +276,11 @@ def _pre_migrate_group(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwarg new_members = [] entry_attrs.setdefault(member_attr, []) for m in entry_attrs[member_attr]: - memberdn = '%s=%s,%s' % (api.Object.user.primary_key.name, - m, - api.env.container_user) + memberdn = DN((api.Object.user.primary_key.name, m), api.env.container_user) new_members.append(ldap.normalize_dn(memberdn)) entry_attrs['member'] = new_members + assert isinstance(dn, DN) attr_blacklist = ['memberofindirect','memberindirect'] attr_blacklist.extend(kwargs.get('attr_blacklist', [])) @@ -330,6 +315,7 @@ def _pre_migrate_group(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwarg def _group_exc_callback(ldap, dn, entry_attrs, exc, options): + assert isinstance(dn, DN) if isinstance(exc, errors.DuplicateEntry): if options.get('groupoverwritegid', False) and \ entry_attrs.get('gidnumber') is not None: @@ -424,24 +410,24 @@ class migrate_ds(Command): ) takes_options = ( - Str('binddn?', validate_dn_param, + DNParam('binddn?', cli_name='bind_dn', label=_('Bind DN'), - default=u'cn=directory manager', + default=DN(('cn', 'directory manager')), autofill=True, ), - Str('usercontainer', validate_dn_param, + DNParam('usercontainer', cli_name='user_container', label=_('User container'), doc=_('DN of container for users in DS relative to base DN'), - default=u'ou=people', + default=DN(('ou', 'people')), autofill=True, ), - Str('groupcontainer', validate_dn_param, + DNParam('groupcontainer', cli_name='group_container', label=_('Group container'), doc=_('DN of container for groups in DS relative to base DN'), - default=u'ou=groups', + default=DN(('ou', 'groups')), autofill=True, ), Str('userobjectclass+', @@ -511,7 +497,7 @@ class migrate_ds(Command): doc=_('Continuous operation mode. Errors are reported but the process continues'), default=False, ), - Str('basedn?', + DNParam('basedn?', cli_name='base_dn', label=_('Base DN'), doc=_('Base DN on remote LDAP server'), @@ -594,11 +580,9 @@ can use their Kerberos accounts.''') def _get_search_bases(self, options, ds_base_dn, migrate_order): search_bases = dict() - ds_base_dn = DN(ds_base_dn) for ldap_obj_name in migrate_order: container = options.get('%scontainer' % to_cli(ldap_obj_name)) if container: - container = DN(container) # Don't append base dn if user already appended it in the container dn if container.endswith(ds_base_dn): search_base = container @@ -606,13 +590,14 @@ can use their Kerberos accounts.''') search_base = DN(container, ds_base_dn) else: search_base = ds_base_dn - search_bases[ldap_obj_name] = str(search_base) + search_bases[ldap_obj_name] = search_base return search_bases def migrate(self, ldap, config, ds_ldap, ds_base_dn, options): """ Migrate objects from DS to LDAP. """ + assert isinstance(ds_base_dn, DN) migrated = {} # {'OBJ': ['PKEY1', 'PKEY2', ...], ...} failed = {} # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...} search_bases = self._get_search_bases(options, ds_base_dn, self.migrate_order) @@ -703,6 +688,7 @@ can use their Kerberos accounts.''') continue dn = ldap_obj.get_dn(pkey) + assert isinstance(dn, DN) entry_attrs['objectclass'] = list( set( config.get( @@ -722,6 +708,7 @@ can use their Kerberos accounts.''') valid_gids = valid_gids, **blacklists ) + assert isinstance(dn, DN) if not dn: continue except errors.NotFound, e: @@ -760,6 +747,8 @@ can use their Kerberos accounts.''') config = ldap.get_ipa_config()[1] ds_base_dn = options.get('basedn') + if ds_base_dn is not None: + assert isinstance(ds_base_dn, DN) # check if migration mode is enabled if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE': @@ -773,6 +762,7 @@ can use their Kerberos accounts.''') if not options.get('compat'): try: (dn,check_compat) = ldap.get_entry(_compat_dn, normalize=False) + assert isinstance(dn, DN) if check_compat is not None and \ check_compat.get('nsslapd-pluginenabled', [''])[0].lower() == 'on': return dict(result={}, failed={}, enabled=True, compat=False) @@ -782,14 +772,16 @@ can use their Kerberos accounts.''') if not ds_base_dn: # retrieve base DN from remote LDAP server (entries, truncated) = ds_ldap.find_entries( - '', ['namingcontexts', 'defaultnamingcontext'], '', + '', ['namingcontexts', 'defaultnamingcontext'], DN(''), _ldap.SCOPE_BASE, size_limit=-1, time_limit=0, ) if 'defaultnamingcontext' in entries[0][1]: - ds_base_dn = entries[0][1]['defaultnamingcontext'][0] + ds_base_dn = DN(entries[0][1]['defaultnamingcontext'][0]) + assert isinstance(ds_base_dn, DN) else: try: - ds_base_dn = entries[0][1]['namingcontexts'][0] + ds_base_dn = DN(entries[0][1]['namingcontexts'][0]) + assert isinstance(ds_base_dn, DN) except (IndexError, KeyError), e: raise StandardError(str(e)) diff --git a/ipalib/plugins/netgroup.py b/ipalib/plugins/netgroup.py index 4236feeb7..263d66b55 100644 --- a/ipalib/plugins/netgroup.py +++ b/ipalib/plugins/netgroup.py @@ -162,6 +162,7 @@ class netgroup_add(LDAPCreate): u'Hostgroups and netgroups share a common namespace') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) entry_attrs.setdefault('nisdomainname', self.api.env.domain) try: @@ -203,6 +204,7 @@ class netgroup_mod(LDAPUpdate): msg_summary = _('Modified netgroup "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, attrs_list) except errors.NotFound: @@ -238,6 +240,7 @@ class netgroup_find(LDAPSearch): ) def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) # Do not display private mepManagedEntry netgroups by default # If looking for managed groups, we need to omit the negation search filter @@ -267,8 +270,11 @@ class netgroup_add_member(LDAPAddMember): member_attributes = ['memberuser', 'memberhost', 'member'] has_output_params = LDAPAddMember.has_output_params + output_params def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) return add_external_pre_callback('host', ldap, dn, keys, options) + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return add_external_post_callback('memberhost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(netgroup_add_member) @@ -280,6 +286,7 @@ class netgroup_remove_member(LDAPRemoveMember): member_attributes = ['memberuser', 'memberhost', 'member'] has_output_params = LDAPRemoveMember.has_output_params + output_params def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return remove_external_post_callback('memberhost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(netgroup_remove_member) diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py index 9bee314ec..2c83560b1 100644 --- a/ipalib/plugins/passwd.py +++ b/ipalib/plugins/passwd.py @@ -24,6 +24,7 @@ from ipalib import _ from ipalib import output from ipalib.plugins.user import split_principal, validate_principal, normalize_principal from ipalib.request import context +from ipapython.dn import DN __doc__ = _(""" Set a user's password @@ -104,7 +105,7 @@ class passwd(Command): (dn, entry_attrs) = ldap.find_entry_by_attr( 'krbprincipalname', principal, 'posixaccount', [''], - ",".join([api.env.container_user, api.env.basedn]) + DN(api.env.container_user, api.env.basedn) ) if principal == getattr(context, 'principal') and \ diff --git a/ipalib/plugins/permission.py b/ipalib/plugins/permission.py index 89f9eaa62..befa74df8 100644 --- a/ipalib/plugins/permission.py +++ b/ipalib/plugins/permission.py @@ -22,6 +22,7 @@ from ipalib import api, _, ngettext from ipalib import Flag, Str, StrEnum from ipalib.request import context from ipalib import errors +from ipapython.dn import DN, EditableDN __doc__ = _(""" Permissions @@ -202,6 +203,7 @@ class permission_add(LDAPCreate): has_output_params = LDAPCreate.has_output_params + output_params def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # Test the ACI before going any further opts = self.obj.filter_aci_attributes(options) opts['test'] = True @@ -219,6 +221,7 @@ class permission_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) # Now actually add the aci. opts = self.obj.filter_aci_attributes(options) opts['test'] = False @@ -275,6 +278,7 @@ class permission_add_noaci(LDAPCreate): yield option def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) permission_type = options.get('permissiontype') if permission_type: entry_attrs['ipapermissiontype'] = [ permission_type ] @@ -297,6 +301,7 @@ class permission_del(LDAPDelete): ) def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) if not options.get('force') and not self.obj.check_system(ldap, dn, *keys): raise errors.ACIError(info='A SYSTEM permission may not be removed') # remove permission even when the underlying ACI is missing @@ -316,6 +321,7 @@ class permission_mod(LDAPUpdate): has_output_params = LDAPUpdate.has_output_params + output_params def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if not self.obj.check_system(ldap, dn, *keys): raise errors.ACIError(info='A SYSTEM permission may not be modified') @@ -332,10 +338,13 @@ class permission_mod(LDAPUpdate): if 'rename' in options: if options['rename']: try: - new_dn = dn.replace(keys[-1].lower(), options['rename'], 1) - (new_dn, attrs) = ldap.get_entry( - new_dn, attrs_list, normalize=self.obj.normalize_dn - ) + try: + new_dn = EditableDN(dn) + new_dn[0]['cn'] # assure the first RDN has cn as it's type + except (IndexError, KeyError), e: + raise ValueError("expected dn starting with 'cn=' but got '%s'" % dn) + new_dn[0].value = options['rename'] + (new_dn, attrs) = ldap.get_entry(new_dn, attrs_list, normalize=self.obj.normalize_dn) raise errors.DuplicateEntry() except errors.NotFound: pass # permission may be renamed, continue @@ -371,6 +380,7 @@ class permission_mod(LDAPUpdate): raise exc def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) # rename the underlying ACI after the change to permission cn = keys[-1] @@ -480,6 +490,7 @@ class permission_show(LDAPRetrieve): has_output_params = LDAPRetrieve.has_output_params + output_params def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) try: common_options = filter_options(options, ['all', 'raw']) aci = self.api.Command.aci_show(keys[-1], aciprefix=ACI_PREFIX, diff --git a/ipalib/plugins/pkinit.py b/ipalib/plugins/pkinit.py index cb3b9c2fc..2d11f0244 100644 --- a/ipalib/plugins/pkinit.py +++ b/ipalib/plugins/pkinit.py @@ -21,6 +21,7 @@ from ipalib import api, errors from ipalib import Int, Str from ipalib import Object, Command from ipalib import _ +from ipapython.dn import DN __doc__ = _(""" Kerberos pkinit options @@ -67,9 +68,7 @@ class pkinit_anonymous(Command): __doc__ = _('Enable or Disable Anonymous PKINIT.') princ_name = 'WELLKNOWN/ANONYMOUS@%s' % api.env.realm - default_dn = 'krbprincipalname=%s,cn=%s,cn=kerberos,%s' % ( - princ_name, api.env.realm, api.env.basedn - ) + default_dn = DN(('krbprincipalname', princ_name), ('cn', api.env.realm), ('cn', 'kerberos'), api.env.basedn) takes_args = ( Str('action', valid_arg), diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py index 77e6f2c79..33c8329f7 100644 --- a/ipalib/plugins/pwpolicy.py +++ b/ipalib/plugins/pwpolicy.py @@ -19,11 +19,12 @@ # along with this program. If not, see . from ipalib import api -from ipalib import Int, Str +from ipalib import Int, Str, DNParam from ipalib.plugins.baseldap import * from ipalib import _ from ipalib.request import context from ipapython.ipautil import run +from ipapython.dn import DN from distutils import version __doc__ = _(""" @@ -75,13 +76,13 @@ class cosentry(LDAPObject): """ NO_CLI = True - container_dn = 'cn=costemplates,%s' % api.env.container_accounts + container_dn = DN(('cn', 'costemplates'), api.env.container_accounts) object_class = ['top', 'costemplate', 'extensibleobject', 'krbcontainer'] default_attributes = ['cn', 'cospriority', 'krbpwdpolicyreference'] takes_params = ( Str('cn', primary_key=True), - Str('krbpwdpolicyreference'), + DNParam('krbpwdpolicyreference'), Int('cospriority', minvalue=0), ) @@ -102,8 +103,7 @@ class cosentry(LDAPObject): )['result'] if len(entries) > 0: group_name = self.api.Object.group.get_primary_key_from_dn( - entries[0]['cn'][0] - ) + DN(entries[0]['cn'][0])) raise errors.ValidationError( name='priority', error=self.priority_not_unique_msg % { @@ -119,6 +119,7 @@ class cosentry_add(LDAPCreate): NO_CLI = True def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # check for existence of the group result = self.api.Command.group_show(keys[-1], all=True)['result'] oc = map(lambda x:x.lower(),result['objectclass']) @@ -141,6 +142,7 @@ class cosentry_mod(LDAPUpdate): NO_CLI = True def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) new_cospriority = options.get('cospriority') if new_cospriority is not None: cos_entry = self.api.Command.cosentry_show(keys[-1])['result'] @@ -167,13 +169,13 @@ api.register(cosentry_find) global_policy_name = 'global_policy' -global_policy_dn = 'cn=%s,cn=%s,cn=kerberos,%s' % (global_policy_name, api.env.realm, api.env.basedn) +global_policy_dn = DN(('cn', global_policy_name), ('cn', api.env.realm), ('cn', 'kerberos'), api.env.basedn) class pwpolicy(LDAPObject): """ Password Policy object """ - container_dn = 'cn=%s,cn=kerberos' % api.env.realm + container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos')) object_name = _('password policy') object_name_plural = _('password policies') object_class = ['top', 'nscontainer', 'krbpwdpolicy'] @@ -339,6 +341,7 @@ class pwpolicy_add(LDAPCreate): yield self.obj.primary_key.clone(attribute=True, required=True) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) self.obj.convert_time_on_input(entry_attrs) self.obj.validate_lifetime(entry_attrs, True) self.api.Command.cosentry_add( @@ -348,6 +351,7 @@ class pwpolicy_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.log.info('%r' % entry_attrs) # attribute rights are not allowed for pwpolicy_add self.obj.add_cospriority(entry_attrs, keys[-1], rights=False) @@ -366,7 +370,8 @@ class pwpolicy_del(LDAPDelete): ) def pre_callback(self, ldap, dn, *keys, **options): - if dn.lower() == global_policy_dn.lower(): + assert isinstance(dn, DN) + if dn == global_policy_dn: raise errors.ValidationError( name='group', error=_('cannot delete global password policy') @@ -374,6 +379,7 @@ class pwpolicy_del(LDAPDelete): return dn def post_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) try: self.api.Command.cosentry_del(keys[-1]) except errors.NotFound: @@ -387,6 +393,7 @@ class pwpolicy_mod(LDAPUpdate): __doc__ = _('Modify a group password policy.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) self.obj.convert_time_on_input(entry_attrs) self.obj.validate_lifetime(entry_attrs, False, *keys) setattr(context, 'cosupdate', False) @@ -408,6 +415,7 @@ class pwpolicy_mod(LDAPUpdate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) rights = options.get('all', False) and options.get('rights', False) self.obj.add_cospriority(entry_attrs, keys[-1], rights) self.obj.convert_time_for_output(entry_attrs, **options) @@ -436,6 +444,7 @@ class pwpolicy_show(LDAPRetrieve): ) def pre_callback(self, ldap, dn, attrs_list, *keys, **options): + assert isinstance(dn, DN) if options.get('user') is not None: user_entry = self.api.Command.user_show( options['user'], all=True @@ -445,6 +454,7 @@ class pwpolicy_show(LDAPRetrieve): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) rights = options.get('all', False) and options.get('rights', False) self.obj.add_cospriority(entry_attrs, keys[-1], rights) self.obj.convert_time_for_output(entry_attrs, **options) diff --git a/ipalib/plugins/range.py b/ipalib/plugins/range.py index 39849b661..c1d918679 100644 --- a/ipalib/plugins/range.py +++ b/ipalib/plugins/range.py @@ -96,6 +96,7 @@ class range_add(LDAPCreate): msg_summary = _('Added ID range "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if ('ipanttrusteddomainsid' not in options and 'ipasecondarybaserid' not in options): raise errors.ValidationError(name=_('Range setup'), @@ -110,6 +111,7 @@ class range_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.handle_iparangetype(entry_attrs, options, keep_objectclass=True) return dn @@ -128,6 +130,7 @@ class range_find(LDAPSearch): # Since all range types are stored within separate containers under # 'cn=ranges,cn=etc' search can be done on a one-level scope def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) attrs_list.append('objectclass') return (filters, base_dn, ldap.SCOPE_ONELEVEL) @@ -140,10 +143,12 @@ class range_show(LDAPRetrieve): __doc__ = _('Display information about a range.') def pre_callback(self, ldap, dn, attrs_list, *keys, **options): + assert isinstance(dn, DN) attrs_list.append('objectclass') return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.handle_iparangetype(entry_attrs, options) return dn @@ -153,10 +158,12 @@ class range_mod(LDAPUpdate): msg_summary = _('Modified ID range "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) attrs_list.append('objectclass') return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.handle_iparangetype(entry_attrs, options) return dn @@ -166,4 +173,3 @@ api.register(range_mod) api.register(range_del) api.register(range_find) api.register(range_show) - diff --git a/ipalib/plugins/selinuxusermap.py b/ipalib/plugins/selinuxusermap.py index 160e68c96..988cb4f0b 100644 --- a/ipalib/plugins/selinuxusermap.py +++ b/ipalib/plugins/selinuxusermap.py @@ -234,6 +234,7 @@ class selinuxusermap_add(LDAPCreate): msg_summary = _('Added SELinux User Map "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) # rules are enabled by default entry_attrs['ipaenabledflag'] = 'TRUE' validate_selinuxuser_inlist(ldap, entry_attrs['ipaselinuxuser']) @@ -243,6 +244,7 @@ class selinuxusermap_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj._convert_seealso(ldap, entry_attrs, **options) return dn @@ -264,6 +266,7 @@ class selinuxusermap_mod(LDAPUpdate): msg_summary = _('Modified SELinux User Map "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) try: (_dn, _entry_attrs) = ldap.get_entry(dn, attrs_list) except errors.NotFound: @@ -288,6 +291,7 @@ class selinuxusermap_mod(LDAPUpdate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj._convert_seealso(ldap, entry_attrs, **options) return dn @@ -331,6 +335,7 @@ class selinuxusermap_show(LDAPRetrieve): __doc__ = _('Display the properties of a SELinux User Map rule.') def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj._convert_seealso(ldap, entry_attrs, **options) return dn @@ -398,6 +403,7 @@ class selinuxusermap_add_user(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -428,6 +434,7 @@ class selinuxusermap_add_host(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index 7cb7b8030..213711ab3 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -278,6 +278,7 @@ class service_add(LDAPCreate): ), ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) (service, hostname, realm) = split_principal(keys[-1]) if service.lower() == 'host' and not options['force']: raise errors.HostService() @@ -322,6 +323,7 @@ class service_del(LDAPDelete): msg_summary = _('Deleted service "%(value)s"') member_attributes = ['managedby'] def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) # In the case of services we don't want IPA master services to be # deleted. This is a limited few though. If the user has their own # custom services allow them to manage them. @@ -367,6 +369,7 @@ class service_mod(LDAPUpdate): member_attributes = ['managedby'] def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if 'usercertificate' in options: (service, hostname, realm) = split_principal(keys[-1]) cert = options.get('usercertificate') @@ -386,7 +389,9 @@ class service_mod(LDAPUpdate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) set_certificate_attrs(entry_attrs) + return dn api.register(service_mod) @@ -402,6 +407,7 @@ class service_find(LDAPSearch): has_output_params = LDAPSearch.has_output_params + output_params def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) # lisp style! custom_filter = '(&(objectclass=ipaService)' \ '(!(objectClass=posixAccount))' \ @@ -439,6 +445,7 @@ class service_show(LDAPRetrieve): has_output_params = LDAPRetrieve.has_output_params + output_params def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) self.obj.get_password_attributes(ldap, dn, entry_attrs) set_certificate_attrs(entry_attrs) diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py index 723cce2e4..e2937949b 100644 --- a/ipalib/plugins/sudorule.py +++ b/ipalib/plugins/sudorule.py @@ -246,6 +246,7 @@ class sudorule_add(LDAPCreate): __doc__ = _('Create new Sudo Rule.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) self.obj.check_order_uniqueness(*keys, **options) # Sudo Rules are enabled by default entry_attrs['ipaenabledflag'] = 'TRUE' @@ -269,6 +270,7 @@ class sudorule_mod(LDAPUpdate): msg_summary = _('Modified Sudo Rule "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if 'sudoorder' in options: new_order = options.get('sudoorder') old_entry = self.api.Command.sudorule_show(keys[-1])['result'] @@ -371,6 +373,7 @@ class sudorule_add_allow_command(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -399,6 +402,7 @@ class sudorule_add_deny_command(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -426,6 +430,7 @@ class sudorule_add_user(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -435,6 +440,7 @@ class sudorule_add_user(LDAPAddMember): return add_external_pre_callback('user', ldap, dn, keys, options) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return add_external_post_callback('memberuser', 'user', 'externaluser', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_add_user) @@ -447,6 +453,7 @@ class sudorule_remove_user(LDAPRemoveMember): member_count_out = ('%i object removed.', '%i objects removed.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return remove_external_post_callback('memberuser', 'user', 'externaluser', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_remove_user) @@ -459,6 +466,7 @@ class sudorule_add_host(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) try: (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) except errors.NotFound: @@ -468,6 +476,7 @@ class sudorule_add_host(LDAPAddMember): return add_external_pre_callback('host', ldap, dn, keys, options) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return add_external_post_callback('memberhost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_add_host) @@ -480,6 +489,7 @@ class sudorule_remove_host(LDAPRemoveMember): member_count_out = ('%i object removed.', '%i objects removed.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return remove_external_post_callback('memberhost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_remove_host) @@ -491,6 +501,7 @@ class sudorule_add_runasuser(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) def check_validity(runas): v = unicode(runas) if v.upper() == u'ALL': @@ -521,6 +532,7 @@ class sudorule_add_runasuser(LDAPAddMember): return add_external_pre_callback('user', ldap, dn, keys, options) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return add_external_post_callback('ipasudorunas', 'user', 'ipasudorunasextuser', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_add_runasuser) @@ -533,6 +545,7 @@ class sudorule_remove_runasuser(LDAPRemoveMember): member_count_out = ('%i object removed.', '%i objects removed.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return remove_external_post_callback('ipasudorunas', 'user', 'ipasudorunasextuser', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_remove_runasuser) @@ -545,6 +558,7 @@ class sudorule_add_runasgroup(LDAPAddMember): member_count_out = ('%i object added.', '%i objects added.') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) def check_validity(runas): v = unicode(runas) if v.upper() == u'ALL': @@ -569,6 +583,7 @@ class sudorule_add_runasgroup(LDAPAddMember): return add_external_pre_callback('group', ldap, dn, keys, options) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return add_external_post_callback('ipasudorunasgroup', 'group', 'ipasudorunasextgroup', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_add_runasgroup) @@ -581,6 +596,7 @@ class sudorule_remove_runasgroup(LDAPRemoveMember): member_count_out = ('%i object removed.', '%i objects removed.') def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) return remove_external_post_callback('ipasudorunasgroup', 'group', 'ipasudorunasextgroup', ldap, completed, failed, dn, entry_attrs, keys, options) api.register(sudorule_remove_runasgroup) diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py index acb65b388..a70293bff 100644 --- a/ipalib/plugins/trust.py +++ b/ipalib/plugins/trust.py @@ -107,9 +107,10 @@ class trust(LDAPObject): ) def make_trust_dn(env, trust_type, dn): + assert isinstance(dn, DN) if trust_type in trust.trust_types: container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn) - return unicode(DN(DN(dn)[0], container_dn)) + return DN(dn[0], container_dn) return dn class trust_add(LDAPCreate): @@ -214,6 +215,7 @@ class trust_del(LDAPDelete): msg_summary = _('Deleted trust "%(value)s"') def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) try: result = self.api.Command.trust_show(keys[-1]) except errors.NotFound, e: @@ -226,6 +228,7 @@ class trust_mod(LDAPUpdate): msg_summary = _('Modified trust "%(value)s"') def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) result = None try: result = self.api.Command.trust_show(keys[-1]) @@ -245,6 +248,7 @@ class trust_find(LDAPSearch): # Since all trusts types are stored within separate containers under 'cn=trusts', # search needs to be done on a sub-tree scope def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options): + assert isinstance(base_dn, DN) return (filters, base_dn, ldap.SCOPE_SUBTREE) class trust_show(LDAPRetrieve): @@ -271,6 +275,7 @@ class trust_show(LDAPRetrieve): return result def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if 'trust_show_type' in options: return make_trust_dn(self.env, options['trust_show_type'], dn) return dn @@ -281,4 +286,3 @@ api.register(trust_mod) api.register(trust_del) api.register(trust_find) api.register(trust_show) - diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index c19d9a666..529699f9a 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -375,11 +375,12 @@ class user(LDAPObject): if not manager: return None - if isinstance(manager, basestring): + if not isinstance(manager, list): manager = [manager] try: + container_dn = DN(self.container_dn, api.env.basedn) for m in xrange(len(manager)): - if manager[m].endswith('%s,%s' % (self.container_dn, api.env.basedn)): + if isinstance(manager[m], DN) and manager[m].endswith(container_dn): continue (dn, entry_attrs) = self.backend.find_entry_by_attr( self.primary_key.name, manager[m], self.object_class, [''], @@ -420,6 +421,7 @@ class user_add(LDAPCreate): ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if not options.get('noprivate', False): try: # The Managed Entries plugin will allow a user to be created @@ -459,7 +461,9 @@ class user_add(LDAPCreate): homes_root = config.get('ipahomesrootdir', ['/home'])[0] # build user's home directory based on his uid entry_attrs['homedirectory'] = posixpath.join(homes_root, keys[-1]) - entry_attrs.setdefault('krbpwdpolicyreference', 'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)) + entry_attrs.setdefault('krbpwdpolicyreference', + DN(('cn', 'global_policy'), ('cn', api.env.realm), ('cn', 'kerberos'), + api.env.basedn)) entry_attrs.setdefault('krbprincipalname', '%s@%s' % (entry_attrs['uid'], api.env.realm)) if entry_attrs.get('gidnumber', DNA_MAGIC) == DNA_MAGIC: @@ -496,6 +500,7 @@ class user_add(LDAPCreate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) config = ldap.get_ipa_config()[1] # add the user we just created into the default primary group def_primary_group = config.get('ipadefaultprimarygroup') @@ -544,6 +549,7 @@ class user_del(LDAPDelete): msg_summary = _('Deleted user "%(value)s"') def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) protected_group_name = u'admins' result = api.Command.group_show(protected_group_name) if result['result'].get('member_user', []) == [keys[-1]]: @@ -562,6 +568,7 @@ class user_mod(LDAPUpdate): has_output_params = LDAPUpdate.has_output_params + user_output_params def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + assert isinstance(dn, DN) if options.get('rename') is not None: config = ldap.get_ipa_config()[1] if 'ipamaxusernamelength' in config: @@ -592,6 +599,7 @@ class user_mod(LDAPUpdate): return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) if options.get('random', False): try: entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword')) @@ -621,6 +629,7 @@ class user_find(LDAPSearch): ) def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options): + assert isinstance(base_dn, DN) if options.get('whoami'): return ("(&(objectclass=posixaccount)(krbprincipalname=%s))"%\ getattr(context, 'principal'), base_dn, scope) @@ -651,6 +660,7 @@ class user_show(LDAPRetrieve): has_output_params = LDAPRetrieve.has_output_params + user_output_params def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) convert_nsaccountlock(entry_attrs) self.obj._convert_manager(entry_attrs, **options) self.obj.get_password_attributes(ldap, dn, entry_attrs) @@ -763,7 +773,7 @@ class user_status(LDAPQuery): # Get list of masters try: (masters, truncated) = ldap.find_entries( - None, ['*'], 'cn=masters,cn=ipa,cn=etc,%s' % api.env.basedn, + None, ['*'], DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn), ldap.SCOPE_ONELEVEL ) except errors.NotFound: diff --git a/ipalib/plugins/virtual.py b/ipalib/plugins/virtual.py index c827d3d9d..8db1a9653 100644 --- a/ipalib/plugins/virtual.py +++ b/ipalib/plugins/virtual.py @@ -23,6 +23,7 @@ Base classes for non-LDAP backend plugins. from ipalib import api from ipalib import Command from ipalib import errors +from ipapython.dn import DN class VirtualCommand(Command): """ @@ -55,7 +56,7 @@ class VirtualCommand(Command): ldap = self.api.Backend.ldap2 self.log.debug("IPA: virtual verify %s" % operation) - operationdn = "cn=%s,%s,%s" % (operation, self.api.env.container_virtual, self.api.env.basedn) + operationdn = DN(('cn', operation), self.api.env.container_virtual, self.api.env.basedn) try: if not ldap.can_write(operationdn, "objectclass"): diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 8a6a51088..a22fae505 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -57,6 +57,7 @@ from nss.error import NSPRError from urllib2 import urlparse from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT_EXPIRED, \ KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, KRB5_REALM_CANT_RESOLVE +from ipapython.dn import DN COOKIE_NAME = 'ipa_session_cookie:%s' @@ -83,7 +84,7 @@ def xml_wrap(value): """ if type(value) in (list, tuple): return tuple(xml_wrap(v) for v in value) - if type(value) is dict: + if isinstance(value, dict): return dict( (k, xml_wrap(v)) for (k, v) in value.iteritems() ) @@ -92,6 +93,8 @@ def xml_wrap(value): if type(value) is Decimal: # transfer Decimal as a string return unicode(value) + if isinstance(value, DN): + return str(value) assert type(value) in (unicode, int, float, bool, NoneType) return value diff --git a/ipalib/session.py b/ipalib/session.py index 4b783bbcb..87fa44825 100644 --- a/ipalib/session.py +++ b/ipalib/session.py @@ -628,7 +628,7 @@ mod_auth_kerb. Everything else remains the same. default_max_session_duration = 60*60 # number of seconds -ISO8601_DATETIME_FMT = '%Y-%m-%dT%H:%M:%S' # FIXME jrd, this should be defined elsewhere +ISO8601_DATETIME_FMT = '%Y-%m-%dT%H:%M:%S' # FIXME, this should be defined elsewhere def fmt_time(timestamp): return time.strftime(ISO8601_DATETIME_FMT, time.localtime(timestamp)) diff --git a/ipalib/util.py b/ipalib/util.py index 039ffb06d..abd50da7d 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -33,8 +33,8 @@ from dns.exception import DNSException from ipalib import errors from ipalib.text import _ -from ipalib.dn import DN, RDN from ipapython.ipautil import decode_ssh_pubkey +from ipapython.dn import DN, RDN def json_serialize(obj): @@ -472,10 +472,3 @@ def validate_rdn_param(ugettext, value): except Exception, e: return str(e) return None - -def validate_dn_param(ugettext, value): - try: - rdn = DN(value) - except Exception, e: - return str(e) - return None diff --git a/ipalib/x509.py b/ipalib/x509.py index 1b133adfb..84c1acfcd 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -42,7 +42,7 @@ from ipalib import api from ipalib import _ from ipalib import util from ipalib import errors -from ipalib.dn import DN +from ipapython.dn import DN PEM = 0 DER = 1 diff --git a/ipapython/dn.py b/ipapython/dn.py new file mode 100644 index 000000000..6cce40e29 --- /dev/null +++ b/ipapython/dn.py @@ -0,0 +1,1643 @@ +# Authors: +# John Dennis +# +# Copyright (C) 2011 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 . + +''' + +Goal +---- + +To allow a Python programmer the ability to operate on DN's +(Distinguished Names) in a simple intuitive manner supporting all the +Pythonic mechanisms for manipulating objects such that the simple +majority case remains simple with simple code, yet the corner cases +are fully supported. With the result both simple and complex cases are +100% correct. + +This is achieved with a fair of amount of syntax sugar which is best +described as "Do What I Mean" (i.e. DWIM). The class implementations +take simple expressions and internally convert them to their more +complex full definitions hiding much of the complexity from the +programmer. + +Anatomy of a DN +--------------- + +Some definitions: + +AVA + An AVA is an Attribute Value Assertion. In more simple terms it's + an attribute value pair typically expressed as attr=value + (e.g. cn=Bob). Both the attr and value in an AVA when expressed in + a string representation are subject to encoding rules. + +RDN + A RDN is a Relative Distinguished Name. A RDN is a non-empty set of + AVA's. In the common case a RDN is single valued consisting of 1 + AVA (e.g. cn=Bob). But a RDN may be multi-valued consisting of + more than one AVA. Because the RDN is a set of AVA's the AVA's are + unordered when they appear in a multi-valued RDN. In the string + representation of a RDN AVA's are separated by the plus sign (+). + +DN + A DN is a ordered sequence of 1 or more RDN's. In the string + representation of a DN each RDN is separated by a comma (,) + +Thus a DN is: + +Sequence of set of pairs + +The following are valid DN's + +# 1 RDN with 1 AVA (e.g. cn=Bob) +RDN(AVA) + +# 2 RDN's each with 1 AVA (e.g. cn=Bob,dc=redhat.com) +RDN(AVA),RDN(AVA) + +# 2 RDN's the first RDN is multi-valued with 2 AVA's +# the second RDN is singled valued with 1 AVA +# (e.g. cn=Bob+ou=people,dc=redhat.com +RDN({AVA,AVA}),RDN(AVA) + +Common programming mistakes +--------------------------- + +DN's present a pernicious problem for programmers. They appear to have +a very simple string format in the majority case, a sequence of +attr=value pairs separated by commas. For example: + +dn='cn=Bob,ou=people,dc=redhat,dc=com' + +As such there is a tendency to believe you can form DN's by simple +string manipulations such as: + +dn='%s=%s' % ('cn','Bob') + ',ou=people,dc=redhat,dc=com' + +Or to extract a attr & value by searching the string, for example: + +attr=dn[0 : dn.find('=')] +value=dn[dn.find('=')+1 : dn.find(',')] + +Or compare a value returned by an LDAP query to a known value: + +if value == 'Bob' + +All of these simple coding assumptions are WRONG and will FAIL when a +DN is not one of the simple DN's (simple DN's are probably the 95% of +all DN's). This is what makes DN handling pernicious. What works in +95% of the cases and is simple, fails for the 5% of DN's which are not +simple. + +Examples of where the simple assumptions fail are: + +* A RDN may be multi-valued + +* A multi-valued RDN has no ordering on it's components + +* Attr's and values must be UTF-8 encoded + +* String representations of AVA's, RDN's and DN's must be completely UTF-8 + +* An attr or value may have reserved characters which must be escaped. + +* Whitespace needs special handling + +To complicate matters a bit more the RFC for the string representation +of DN's (RFC 4514) permits a variety of different syntax's each of +which can evaluate to exactly the same DN but have different string +representations. For example, the attr "r,w" which contains a reserved +character (the comma) can be encoded as a string in these different +ways: + +'r\,w' # backslash escape +'r\2cw' # hexadecimal ascii escape +'#722C77' # binary encoded + +It should be clear a DN string may NOT be a simple string, rather a DN +string is ENCODED. For simple strings the encoding of the DN is +identical to the simple string value (this common case leads to +erroneous assumptions and bugs because it does not account for +encodings). + +The openldap library we use at the client level uses the backslash +escape form. The LDAP server we use uses the hexadecimal ascii escape +form. Thus 'r,w' appears as 'r\,w' when sent from the client to the +LDAP server as part of a DN. But when it's returned as a DN from the +server in an LDAP search it's returned as 'r\2cw'. Any attempt to +compare 'r\,w' to 'r\2cw' for equality will fail despite the fact they +are indeed equal once decoded. Such a test fails because you're +comparing two different encodings of the same value. In MIME you +wouldn't expect the base64 encoding of a string to be equal to the +same string encoded as quoted-printable would you? + +When you are comparing attrs or values which are part of a DN and +other string you MUST: + +* Know if either of the strings have been encoded and make sure you're + comparing only decoded components component-wise. + +* Extract the component from the DN and decode it. You CANNOT decode + the entire DN as a string and operate on it. Why? Consider a value + with a comma embedded in it. For example: + + cn=r\2cw,cn=privilege + + Is a DN with 2 RDN components: cn=r,w followed by "cn=privilege" + + But if you decode the entire DN string as a whole you would get: + + cn=r,w,cn=privilege + + Which is a malformed DN with 3 RDN's, the 2nd RDN is invalid. + +* Determine if a RDN is multi-valued, if so you must account + for the fact each AVA component in the multi-valued RDN can appear + in any order and still be equivalent. For example the following two + RDN's are equal: + + cn=Bob+ou=people + ou=people+cn=Bob + + In addition each AVA (cn=Bob & ou=people) needs to be + INDEPENDENTLY decoded prior to comparing the unordered set of AVA's + in the multi-valued RDN. + +If you are trying to form a new DN or RDN from a raw string you cannot +simply do string concatenation or string formatting unless you ESCAPE +the components independently prior to concatenation, for example: + + base = 'dc=redhat,dc=com' + value = 'r,w' + dn = 'cn=%s,%s' % (value, base) + +Will result in the malformed DN 'cn=r,w,dc=redhat,dc=com' + +Syntax Sugar +------------ + +The majority of DN's have a simple string form: + +attr=value,attr=value + +We want the programmer to be able to create DN's, compare them, and +operate on their components as simply and concisely as possible so +the classes are implemented to provide a lot of syntax sugar. + +The classes automatically handle UTF-8 <-> Unicode conversions. Every +attr and value which is returned from a class will be Unicode. Every +attr and value assigned into an object will be promoted to +Unicode. All string representations in RFC 4514 format will be UTF-8 +and properly escaped. Thus at the "user" or "API" level every string +is Unicode with the single exception that the str() method returns RFC +compliant escaped UTF-8. + +RDN's are assumed to be single-valued. If you need a multi-valued RDN +(an exception) you must explicitly create a multi-valued RDN. + +Thus DN's are assumed to be a sequence of attr, value pairs, which is +equivalent to a sequence of RDN's. The attr and value in the pair MUST +be strings. + +The DN and RDN constructors take a sequence, the constructor parses +the sequence to find items it knows about. + +The DN constructor will accept in it's sequence: + * tuple of 2 strings, converting it to an RDN + * list of 2 strings, converting it to an RDN + * a RDN object + * a DN syntax string (e.g. 'cn=Bob,dc=redhat.com') + +Note DN syntax strings should be avoided if possible when passing to a +constructor because they run afoul of the problems outlined above +which the DN, RDN & AVA classes are meant to overcome. But sometimes a +DN syntax string is all you have to work with. DN strings which come +from a LDAP library or server will be properly formed and it's safe to +use those. However DN strings provided via user input should be +treated suspiciously as they may be improperly formed. You can test +for this by passing the string to the DN constructor and see if it +throws an exception. + +The sequence passed to the DN constructor takes each item in order, +produces one or more RDN's from it and appends those RDN in order to +its internal RDN sequence. + +For example: + + DN(('cn', 'Bob'), ('dc', 'redhat.com')) + +This is equivalent to the DN string: + + cn=Bob,dc=redhat.com + +And is exactly equal to: + + DN(RDN(AVA('cn','Bob')),RDN(AVA('dc','redhat.com'))) + +The following are alternative syntax's which are all exactly +equivalent to the above example. + + DN(['cn', 'Bob'], ['dc', 'redhat.com']) + DN(RDN('cn', 'Bob'), RDN('dc', 'redhat.com')) + +You can provide a properly escaped string representation. + + DN('cn=Bob,dc=redhat.com') + +You can mix and match any of the forms in the constructor parameter +list. + + DN(('cn', 'Bob'), 'dc=redhat.com') + DN(('cn', 'Bob'), RDN('dc', 'redhat.com')) + +AVA's have an attr and value property, thus if you have an AVA + +# Get the attr and value +ava.attr -> u'cn' +ava.value -> u'Bob' + +# Set the attr and value +ava.attr = 'cn' +ava.value = 'Bob' + +Since RDN's are assumed to be single valued, exactly the same +behavior applies to an RDN. If the RDN is multi-valued then the attr +property returns the attr of the first AVA, likewise for the value. + +# Get the attr and value +rdn.attr -> u'cn' +rdn.value -> u'Bob' + +# Set the attr and value +rdn.attr = 'cn' +rdn.value = 'Bob' + +Also RDN's can be indexed by name or position (see the RDN class doc +for details). + +rdn['cn'] -> u'Bob' +rdn[0] -> AVA('cn', 'Bob') + +A DN is a sequence of RDN's, as such any of Python's container +operators can be applied to a DN in a intuitive way. + +# How many RDN's in a DN? +len(dn) + +# WARNING, this a count of RDN's not how characters there are in the +# string representation the dn, instead that would be: +len(str(dn)) + +# Iterate over each RDN in a DN +for rdn in dn: + +# Get the first RDN in a DN +dn[0] -> RDN('cn', 'Bob') + +# Get the value of the first RDN in a DN +dn[0].value -> u'Bob' + +# Get the value of the first RDN by indexing by attr name +dn['cn'] -> u'Bob' + +# WARNING, when a string is used as an index key the FIRST RDN's value +# in the sequence whose attr matches the key is returned. Thus if you +# have a DN like this "cn=foo,cn=bar" then dn['cn'] will always return +# 'foo' even though there is another attr with the name 'cn'. This is +# almost always what the programmer wants. See the class doc for how +# you can override this default behavior and get a list of every value +# whose attr matches the key. + +# Set the first RDN in the DN (all are equivalent) +dn[0] = ('cn', 'Bob') +dn[0] = ['cn', 'Bob'] +dn[0] = RDN('cn', 'Bob') + +dn[0].attr = 'cn' +dn[0].value = 'Bob' + +# Get the first two RDN's using slices +dn[0:2] + +# Get the last two RDN's using slices +dn[-2:] + +# Get a list of all RDN's using slices +dn[:] + +# Set the 2nd and 3rd RDN using slices (all are equivalent) +dn[1:3] = ('cn', 'Bob), ('dc', 'redhat.com') +dn[1:3] = RDN('cn', 'Bob), RDN('dc', 'redhat.com') + +String representations and escapes: + +# To get an RFC compliant string representation of a DN, RDN or AVA +# simply call str() on it or evaluate it in a string context. +str(dn) -> 'cn=Bob,dc=redhat.com' + +# When working with attr's and values you do not have to worry about +# escapes, simply use the raw unescaped string in a natural fashion. + +rdn = RDN('cn', 'r,w') + +# Thus: +rdn.value == 'r,w' -> True + +# But: +str(rdn) == 'cn=r,w' -> False +# Because: +str(rdn) -> 'cn=r\2cw' or 'cn='r\,w' # depending on the underlying LDAP library + +Equality and Comparing: + +# All DN's, RDN's and AVA's support equality testing in an intuitive +# manner. +dn1 = DN(('cn', 'Bob')) +dn2 = DN(RDN('cn', 'Bob')) +dn1 == dn2 -> True +dn1[0] == dn2[0] -> True +dn1[0].value = 'Bobby' +dn1 == dn2 -> False + +DN objects implement startswith(), endswith() and the "in" membership +operator. You may pass a DN or RDN object to these. Examples: + +if dn.endswith(base_dn): +if dn.startswith(rdn1): +if container_dn in dn: + +# See the class doc for how DN's, RDN's and AVA's compare +# (e.g. cmp()). The general rule is for objects supporting multiple +# values first their lengths are compared, then if the lengths match +# the respective components of each are pair-wise compared until one +# is discovered to be non-equal. The comparision is case insensitive. + +Cloning (Object Copy): + +All the class types are capable of cloning by passing an object of the +same type (or subclass) to the constructor. The new object is a copy +of the object passed as input to the constructor. One place this is +useful is when you want to coerce between immutable and mutable +versions in order to modify an object. + +Concatenation, In-Place Addition, Insertion: + +# DN's and RDN's can be concatenated. +# Return a new DN by appending the RDN's of dn2 to dn1 +dn3 = dn1 + dn2 + +# Append a RDN to DN's RDN sequence (all are equivalent) +dn += ('cn', 'Bob') +dn += RDN('cn', 'Bob') + +# Append a DN to an existing DN +dn1 += dn2 + +# Prepend a RDN to an existing DN +dn1.insert(0, RDN('cn', 'Bob')) + +Finally see the unittest for a more complete set of ways you can +manipulate these objects. + +Mutability +---------- + +Python makes a clear distinction between mutable and immutable +objects. Examples of immutable Python objects are strings, integers +and floats. Examples of mutable Python objects are lists, dicts, and +sets. Immutable objects cannot be modified, mutable objects can be +modified. An object's mutability affects how the object behaves when +passed to a function or method, this is because it's the object's +reference which is always passed, thus immutable objects behave as if +it were "call by value" and mutable objects behave as if it were "call +by reference" (mutable objects can be modifed inside the +function/method and that modification will be visible to the +caller. On object's mutability also affects how an object will behave +when used as a key in a dict or as a member of a set. + +The following discussion applies equally to AVA, RDN and DN object +class variants. + +The AVA, RDN and DN classes have both immutable and mutable +variants. The base classes (AVA, RDN, DN) are immutable. Each of the +immutable base classes have a mutable subclass whose name begins with +'Editable'. Thus the DN class is immutable, instances of that class +cannot be modified, there is a mutable class EditableDN derived from +DN whose instances can be modified. The primary difference between the +immutable and mutable variants is: + +* Immutable variants are preferred. + +* Mutable variants are exactly identical in behavior to their + immutable parent class (except for supporting assignment, etc.) + +* Immutable objects that test as equal will be the same as dict keys + and set members even if they are different objects. Mutable variants + are not hashable and thus cannot be used as a dict key nor inserted + into a set. + +* Only mutable variants support modification via assignment, insert or + in-place addition (e.g. +=). + +* In-place addtion (e.g. +=) works for both immutable and mutable + variants. The distinction is for immutable objects the lhs is + replaced with a new immutable result while a mutable object will be + modfied in place and lhs object remains the same object. + +It is trival to coerce between an mutable and immutable AVA, RDN and +DN types. These classes can clone their objects by passing an object +of the same type to the constructor. For example: + + dn1 = DN(('cn', 'Bob')) # dn1 is immutable + dn2 = EditableDN(dn1) # dn2 is mutable copy of dn1, + # equal to dn1 until it's modified + + and visa-versa + + dn1 = EditableDN(('cn', 'Bob')) # dn1 is mutable + dn2 = DN(dn1) # dn2 is immutable copy of dn1, equal to dn1 + +''' + +from ldap.dn import str2dn, dn2str +from ldap import DECODING_ERROR +import sys + +__all__ = ['AVA', 'EditableAVA', 'RDN', 'EditableRDN', 'DN', 'EditableDN'] + +def _adjust_indices(start, end, length): + 'helper to fixup start/end slice values' + + if end > length: + end = length + elif end < 0: + end += length + if end < 0: + end = 0 + + if start < 0: + start += length + if start < 0: + start = 0 + + return start, end + +class AVA(object): + ''' + AVA(arg0, ...) + + An AVA is an LDAP Attribute Value Assertion. It is convenient to think of + AVA's as a pair. AVA's are members of RDN's (Relative + Distinguished Name). + + The AVA constructor is passed a sequence of args and a set of + keyword parameters used for configuration. + + The arg sequence may be: + + 1) With 2 arguments, the first argument will be the attr, the 2nd + the value. Each argument must be scalar convertable to unicode. + + 2) With a sigle list or tuple argument containing exactly 2 items. + Each item must be scalar convertable to unicode. + + 3) With a single string (or unicode) argument, in this case the string will + be interpretted using the DN syntax described in RFC 4514 to yield a AVA + pair. The parsing recognizes the DN syntax escaping rules. + + For example: + + ava = AVA('cn', 'Bob') # case 1: two strings + ava = AVA(('cn', 'Bob')) # case 2: 2-valued tuple + ava = AVA(['cn', 'Bob']) # case 2: 2-valued list + ava = AVA('cn=Bob') # case 3: DN syntax + + AVA object have two properties for accessing their data: + + attr: the attribute name, cn in our exmaple + value: the attribute's value, Bob in our example + + When attr and value are returned they will always be unicode. When + attr or value are set they will be promoted to unicode. + + AVA objects support indexing by name, e.g. + + ava['cn'] + + returns the value (Bob in our example). If the index does key does not match + the attr then a KeyError will be raised. + + AVA objects support equality testing and comparsion (e.g. cmp()). When they + are compared the attr is compared first, if the 2 attr's are equal then the + values are compared. The comparision is case insensitive (because attr's map + to numeric OID's and their values derive from from the 'name' atribute type + (OID 2.5.4.41) whose EQUALITY MATCH RULE is caseIgnoreMatch. + + The str method of an AVA returns the string representation in RFC 4514 DN + syntax with proper escaping. + ''' + is_mutable = False + flags = 0 + + def __init__(self, *args, **kwds): + if len(args) == 1: + arg = args[0] + if isinstance(arg, AVA): + ava = (arg.attr, arg.value) + elif isinstance(arg, basestring): + try: + rdns = str2dn(arg.encode('utf-8')) + except DECODING_ERROR: + raise ValueError("malformed AVA string = \"%s\"" % arg) + if len(rdns) != 1: + raise ValueError("multiple RDN's specified by \"%s\"" % (arg)) + rdn = rdns[0] + if len(rdn) != 1: + raise ValueError("multiple AVA's specified by \"%s\"" % (arg)) + ava = rdn[0] + elif isinstance(arg, (tuple, list)): + ava = arg + if len(ava) != 2: + raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (ava)) + else: + raise TypeError("with 1 argument, argument must be str,unicode,tuple or list, got %s instead" % \ + arg.__class__.__name__) + + attr = ava[0] + value = ava[1] + elif len(args) == 2: + attr = args[0] + value = args[1] + else: + raise TypeError("takes 1 or 2 arguments (%d given)" % (len(args))) + + self._set_attr(attr) + self._set_value(value) + + def _get_attr(self): + return self._attr_unicode + + def _set_attr(self, new_attr): + # Scalars only + if isinstance(new_attr, (tuple, list)): + raise TypeError("attr must be scalar, got %s" % type(new_attr)) + + try: + if isinstance(new_attr, unicode): + self._attr_unicode = new_attr + elif isinstance(new_attr, str): + self._attr_unicode = new_attr.decode('utf-8') + else: + self._attr_unicode = unicode(new_attr) + except Exception, e: + raise ValueError('unable to convert attr "%s" to unicode: %s' % (new_attr, e)) + + attr = property(_get_attr) + + def _get_value(self): + return self._value_unicode + + def _set_value(self, new_value): + # Scalars only + if isinstance(new_value, (tuple, list)): + raise TypeError("value must be scalar, got %s" % type(new_value)) + + try: + if isinstance(new_value, unicode): + self._value_unicode = new_value + elif isinstance(new_value, str): + self._value_unicode = new_value.decode('utf-8') + else: + self._value_unicode = unicode(new_value) + except Exception, e: + raise ValueError('unable to convert value "%s" to unicode: %s' % (new_value, e)) + + value = property(_get_value) + + def _to_openldap(self): + return [[(self._attr_unicode.encode('utf-8'), self._value_unicode.encode('utf-8'), self.flags)]] + + def __str__(self): + return dn2str(self._to_openldap()) + + def __repr__(self): + return "%s.%s('%s')" % (self.__module__, self.__class__.__name__, self.__str__()) + + def __getitem__(self, key): + if isinstance(key, basestring): + if key == self._attr_unicode: + return self._value_unicode + raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) + else: + raise TypeError("unsupported type for AVA indexing, must be basestring; not %s" % \ + (key.__class__.__name__)) + + def __hash__(self): + # Hash is computed from AVA's string representation because it's immutable + return hash(str(self)) + + def __eq__(self, other): + ''' + The attr comparison is case insensitive because attr is + really an LDAP attribute type which means it's specified with + an OID (dotted number) and not a string. Since OID's are + numeric the human readable name which maps to the OID is not + significant in case. + + The value comparison is also case insensitive because the all + attribute types used in a DN are derived from the 'name' + atribute type (OID 2.5.4.41) whose EQUALITY MATCH RULE is + caseIgnoreMatch. + ''' + # Try coercing string to AVA, if successful compare to coerced object + if isinstance(other, basestring): + try: + other_ava = AVA(other) + return self.__eq__(other_ava) + except Exception: + return False + + # If it's not an AVA it can't be equal + if not isinstance(other, AVA): + return False + + # Perform comparision between objects of same type + return self._attr_unicode.lower() == other.attr.lower() and \ + self._value_unicode.lower() == other.value.lower() + + def __ne__(self, other): + return not self.__eq__(other) + + def __cmp__(self, other): + 'comparision is case insensitive, see __eq__ doc for explanation' + + if not isinstance(other, AVA): + raise TypeError("expected AVA but got %s" % (other.__class__.__name__)) + + result = cmp(self._attr_unicode.lower(), other.attr.lower()) + if result != 0: + return result + result = cmp(self._value_unicode.lower(), other.value.lower()) + return result + +class EditableAVA(AVA): + ''' + Exactly identical to the AVA class except + + * Hash value is based on object identity, not object + value. Objects that test as equal will be non-unique when + used as a dict key or member of a set. + + * The attr and value properties may be modified after object creation. + + ''' + is_mutable = True + __hash__ = None + + attr = property(AVA._get_attr, AVA._set_attr) + value = property(AVA._get_value, AVA._set_value) + + + +class RDN(object): + ''' + RDN(arg0, ...) + + An RDN is a LDAP Relative Distinguished Name. RDN's are members of DN's + (Distinguished Name). An RDN contains 1 or more AVA's. If the RDN contains + more than one AVA it is said to be a multi-valued RDN. When an RDN is + multi-valued the AVA's are unorderd comprising a set. However this + implementation orders the AVA's according to the AVA comparison function to + make equality and comparison testing easier. Think of this a canonical + normalization (however LDAP does not impose any ordering on multiple AVA's + within an RDN). Single valued RDN's are the norm and thus the RDN + constructor has simple syntax for them. + + The RDN constructor is passed a sequence of args and a set of + keyword parameters used for configuration. + + The constructor iterates though the sequence and adds AVA's to the RDN. + + The arg sequence may be: + + * A 2-valued tuple or list denotes the pair of an AVA. The + first member is the attr and the second member is the value, both members + must be strings (or unicode). The tuple or list is passed to the AVA + constructor and the resulting AVA is added to the RDN. Multiple tuples or + lists may appear in the argument list, each adds one additional AVA to the + RDN. + + * A single string (or unicode) argument, in this case the string will + be interpretted using the DN syntax described in RFC 4514 to yield one or + more AVA pairs. The parsing recognizes the DN syntax escaping + rules. + + * A AVA object, the AVA will be copied into the new RDN respecting + the constructors keyword configuration parameters. + + * A RDN object, the AVA's in the RDN are copied into the new RDN + respecting the constructors keyword configuration parameters. + + Single AVA Examples: + + RDN(('cn', 'Bob')) # tuple yields 1 AVA + RDN('cn=Bob') # DN syntax with 1 AVA + RDN(AVA('cn', 'Bob')) # AVA object adds 1 AVA + + Multiple AVA Examples: + + RDN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 AVA's + RDN('cn=Bob+ou=people') # DN syntax with 2 AVA's + RDN(AVA('cn', 'Bob'),AVA('ou', 'people')) # 2 AVA objects adds 2 AVA's + RDN(('cn', 'Bob'), 'ou=people') # 2 args, 1st tuple forms 1 AVA, + # 2nd DN syntax string adds 1 AVA, + # 2 AVA's in total + + Note: The RHS of a slice assignment is interpreted exactly in the + same manner as the constructor argument list (see above examples). + + RDN objects support iteration over their AVA members. You can iterate all + AVA members via any Python iteration syntax. RDN objects support full Python + indexing using bracket [] notation. Examples: + + len(rdn) # return the number of AVA's + rdn[0] # indexing the first AVA + rdn['cn'] # index by AVA attr, returns AVA value + for ava in rdn: # iterate over each AVA + rdn[:] # a slice, in this case a copy of each AVA + + WARNING: When indexing by attr (e.g. rdn['cn']) there is a possibility more + than one AVA has the same attr name as the index key. The default behavior + is to return the value of the first AVA whose attr matches the index + key. + + RDN objects support the AVA attr and value properties as another programmer + convenience because the vast majority of RDN's are single valued. The attr + and value properties return the attr and value properties of the first AVA + in the RDN, for example: + + rdn = RDN(('cn', 'Bob')) # rdn has 1 AVA whose attr == 'cn' and value == 'Bob' + len(rdn) -> 1 + rdn.attr -> u'cn' # exactly equivalent to rdn[0].attr + rdn.value -> u'Bob' # exactly equivalent to rdn[0].value + + When attr and value are returned they will always be unicode. When + attr or value are set they will be promoted to unicode. + + If an RDN is multi-valued the attr and value properties still return only + the first AVA's properties, programmer beware! Recall the AVA's in the RDN + are sorted according the to AVA collating semantics. + + RDN objects support equality testing and comparision. See AVA for the + definition of the comparision method. + + RDN objects support concatenation and addition with other RDN's or AVA's + + rdn1 + rdn2 # yields a new RDN object with the contents of each RDN. + rdn1 + ava1 # yields a new RDN object with the contents of rdn1 and ava1 + + RDN objects can add AVA's objects via in-place addition. + + rdn1 += rdn2 # rdn1 now contains the sum of rdn1 and rdn2 + rdn1 += ava1 # rdn1 has ava1 added to it. + + The str method of an RDN returns the string representation in RFC 4514 DN + syntax with proper escaping. + ''' + + is_mutable = False + flags = 0 + AVA_type = AVA + + def __init__(self, *args, **kwds): + self.avas = self._avas_from_sequence(args) + self.avas.sort() + + def _ava_from_value(self, value): + if isinstance(value, AVA): + return self.AVA_type(value.attr, value.value) + elif isinstance(value, RDN): + avas = [] + for ava in value.avas: + avas.append(self.AVA_type(ava.attr, ava.value)) + if len(avas) == 1: + return avas[0] + else: + return avas + elif isinstance(value, basestring): + try: + rdns = str2dn(value.encode('utf-8')) + if len(rdns) != 1: + raise ValueError("multiple RDN's specified by \"%s\"" % (value)) + rdn = rdns[0] + if len(rdn) == 1: + return self.AVA_type(rdn[0][0], rdn[0][1]) + else: + avas = [] + for ava_tuple in rdn: + avas.append(self.AVA_type(ava_tuple[0], ava_tuple[1])) + return avas + except DECODING_ERROR: + raise ValueError("malformed RDN string = \"%s\"" % value) + elif isinstance(value, (tuple, list)): + if len(value) != 2: + raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (value)) + return self.AVA_type(value) + else: + raise TypeError("must be str,unicode,tuple, or AVA, got %s instead" % \ + value.__class__.__name__) + + + def _avas_from_sequence(self, seq): + avas = [] + + for item in seq: + ava = self._ava_from_value(item) + if isinstance(ava, list): + avas.extend(ava) + else: + avas.append(ava) + return avas + + def _to_openldap(self): + return [[(ava.attr.encode('utf-8'), ava.value.encode('utf-8'), self.flags) for ava in self.avas]] + + def __str__(self): + return dn2str(self._to_openldap()) + + def __repr__(self): + return "%s.%s('%s')" % (self.__module__, self.__class__.__name__, self.__str__()) + + def _next(self): + for ava in self.avas: + yield ava + + def __iter__(self): + return self._next() + + def __len__(self): + return len(self.avas) + + def __getitem__(self, key): + if isinstance(key, (int, long, slice)): + return self.avas[key] + elif isinstance(key, basestring): + for ava in self.avas: + if key == ava.attr: + return ava.value + raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) + else: + raise TypeError("unsupported type for RDN indexing, must be int, basestring or slice; not %s" % \ + (key.__class__.__name__)) + + def _get_attr(self): + if len(self.avas) == 0: + raise IndexError("No AVA's in this RDN") + return self.avas[0].attr + + def _set_attr(self, new_attr): + if len(self.avas) == 0: + raise IndexError("No AVA's in this RDN") + + self.avas[0].attr = new_attr + + attr = property(_get_attr) + + def _get_value(self): + if len(self.avas) == 0: + raise IndexError("No AVA's in this RDN") + return self.avas[0].value + + def _set_value(self, new_value): + if len(self.avas) == 0: + raise IndexError("No AVA's in this RDN") + + self.avas[0].value = new_value + + value = property(_get_value) + + def __hash__(self): + # Hash is computed from RDN's string representation because it's immutable + return hash(str(self)) + + def __eq__(self, other): + # Try coercing string to RDN, if successful compare to coerced object + if isinstance(other, basestring): + try: + other_rdn = RDN(other) + return self.__eq__(other_rdn) + except Exception: + return False + + # If it's not an RDN it can't be equal + if not isinstance(other, RDN): + return False + + # Perform comparision between objects of same type + return self.avas == other.avas + + def __ne__(self, other): + return not self.__eq__(other) + + def __cmp__(self, other): + if not isinstance(other, RDN): + raise TypeError("expected RDN but got %s" % (other.__class__.__name__)) + + result = cmp(len(self), len(other)) + if result != 0: + return result + i = 0 + while i < len(self): + result = cmp(self[i], other[i]) + if result != 0: + return result + i += 1 + return 0 + + def __add__(self, other): + result = self.__class__(self) + if isinstance(other, RDN): + for ava in other.avas: + result.avas.append(self.AVA_type(ava.attr, ava.value)) + elif isinstance(other, AVA): + result.avas.append(self.AVA_type(other.attr, other.value)) + elif isinstance(other, basestring): + rdn = self.__class__(other) + for ava in rdn.avas: + result.avas.append(self.AVA_type(ava.attr, ava.value)) + else: + raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__)) + + result.avas.sort() + return result + +class EditableRDN(RDN): + ''' + Exactly identical to the RDN class except + + * Hash value is based on object identity, not object + value. Objects that test as equal will be non-unique when + used as a dict key or member of a set. + + * AVA components may be assigned via assignment statements. + + * In-place addition modifes the lhs object. + + * The attr and value properties may be modified after object creation. + ''' + + is_mutable = True + __hash__ = None + AVA_type = EditableAVA + + def __setitem__(self, key, value): + if isinstance(key, (int, long)): + new_ava = self._ava_from_value(value) + if isinstance(new_ava, list): + raise TypeError("cannot assign multiple AVA's to single entry") + self.avas[key] = new_ava + elif isinstance(key, slice): + avas = self._avas_from_sequence(value) + self.avas[key] = avas + elif isinstance(key, basestring): + new_ava = self._ava_from_value(value) + if isinstance(new_ava, list): + raise TypeError("cannot assign multiple AVA's to single entry") + found = False + i = 0 + while i < len(self.avas): + if key == self.avas[i].attr: + found = True + self.avas[i] = new_ava + break + i += 1 + if not found: + raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) + else: + raise TypeError("unsupported type for RDN indexing, must be int, basestring or slice; not %s" % \ + (key.__class__.__name__)) + self.avas.sort() + + attr = property(RDN._get_attr, RDN._set_attr) + value = property(RDN._get_value, RDN._set_value) + + + def __iadd__(self, other): + # If __iadd__ is not available Python will emulate += by + # replacing the lhs object with the result of __add__ (if available). + if isinstance(other, RDN): + for ava in other.avas: + self.avas.append(self.AVA_type(ava.attr, ava.value)) + elif isinstance(other, AVA): + self.avas.append(self.AVA_type(other.attr, other.value)) + elif isinstance(other, basestring): + rdn = self.__class__(other) + for ava in rdn.avas: + self.avas.append(self.AVA_type(ava.attr, ava.value)) + else: + raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__)) + + self.avas.sort() + return self + +class DN(object): + ''' + DN(arg0, ...) + + A DN is a LDAP Distinguished Name. A DN is an ordered sequence of RDN's. + + The DN constructor is passed a sequence of args and a set of + keyword parameters used for configuration. normalize means the + attr and value will be converted to lower case. + + The constructor iterates through the sequence and adds the RDN's + it finds in order to the DN object. Each item in the sequence may + be: + + * A 2-valued tuple or list. The first member is the attr and the + second member is the value of an RDN, both members must be + strings (or unicode). The tuple or list is passed to the RDN + constructor and the resulting RDN is appended to the + DN. Multiple tuples or lists may appear in the argument list, + each adds one additional RDN to the DN. + + * A single string (or unicode) argument, in this case the string + will be interpretted using the DN syntax described in RFC 4514 + to yield one or more RDN's which will be appended in order to + the DN. The parsing recognizes the DN syntax escaping rules. + + * A RDN object, the RDN will copied respecting the constructors + keyword configuration parameters and appended in order. + + * A DN object, the RDN's in the DN are copied respecting the + constructors keyword configuration parameters and appended in + order. + + Single DN Examples: + + DN(('cn', 'Bob')) # tuple yields 1 RDN + DN(['cn', 'Bob']) # list yields 1 RDN + DN('cn=Bob') # DN syntax with 1 RDN + DN(RDN('cn', 'Bob')) # RDN object adds 1 RDN + + Multiple RDN Examples: + + DN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 RDN's + # 2 RDN's total + DN('cn=Bob,ou=people') # DN syntax with 2 RDN's + # 2 RDN's total + DN(RDN('cn', 'Bob'),RDN('ou', 'people')) # 2 RDN objects + # 2 RDN's total + DN(('cn', 'Bob'), "ou=people') # 1st tuple adds 1 RDN + # 2nd DN syntax string adds 1 RDN + # 2 RDN's total + base_dn = DN('dc=redhat,dc=com') + container_dn = DN('cn=sudorules,cn=sudo') + DN(('cn', 'Bob'), container_dn, base_dn) + # 1st arg adds 1 RDN, cn=Bob + # 2nd arg adds 2 RDN's, cn=sudorules,cn=sudo + # 3rd arg adds 2 RDN's, dc=redhat,dc=com + # 5 RDN's total + + + Note: The RHS of a slice assignment is interpreted exactly in the + same manner as the constructor argument list (see above examples). + + DN objects support iteration over their RDN members. You can iterate all + RDN members via any Python iteration syntax. DN objects support full Python + indexing using bracket [] notation. Examples: + + len(rdn) # return the number of RDN's + rdn[0] # indexing the first RDN + rdn['cn'] # index by RDN attr, returns RDN value + for ava in rdn: # iterate over each RDN + rdn[:] # a slice, in this case a copy of each RDN + + WARNING: When indexing by attr (e.g. dn['cn']) there is a + possibility more than one RDN has the same attr name as the index + key. The default behavior is to return the value of the first RDN + whose attr matches the index key. If it's important the attr + belong to a specific RDN (e.g. the first) then this is the + suggested construct: + + try: + cn = dn[0]['cn'] + except (IndexError, KeyError): + raise ValueError("dn '%s' missing expected cn as first attribute" % dn) + + The IndexError catches a DN which does not have the expected + number of RDN's and the KeyError catches the case where the + indexed RDN does not have the expected attr. + + DN object support slices. + + # Get the first two RDN's using slices + dn[0:2] + + # Get the last two RDN's using slices + dn[-2:] + + # Get a list of all RDN's using slices + dn[:] + + # Set the 2nd and 3rd RDN using slices (all are equivalent) + dn[1:3] = ('cn', 'Bob'), ('dc', 'redhat.com') + dn[1:3] = [['cn', 'Bob'], ['dc', 'redhat.com']] + dn[1:3] = RDN('cn', 'Bob'), RDN('dc', 'redhat.com') + + DN objects support the insert operation. + + dn.insert(i,x) is exactly equivalent to dn[i:i] = [x], thus the following + are all equivalent: + + dn.insert(i, ('cn','Bob')) + dn.insert(i, ['cn','Bob']) + dn.insert(i, RDN(('cn','Bob'))) + dn[i:i] = [('cn','Bob')] + + DN objects support equality testing and comparision. See RDN for the + definition of the comparision method. + + DN objects implement startswith(), endswith() and the "in" membership + operator. You may pass a DN or RDN object to these. Examples: + + # Test if dn ends with the contents of base_dn + if dn.endswith(base_dn): + # Test if dn starts with a rdn + if dn.startswith(rdn1): + # Test if a container is present in a dn + if container_dn in dn: + + DN objects support concatenation and addition with other DN's or RDN's + or strings (interpreted as RFC 4514 DN syntax). + + # yields a new DN object with the RDN's of dn2 appended to the RDN's of dn1 + dn1 + dn2 + + # yields a new DN object with the rdn1 appended to the RDN's of dn1 + dn1 + rdn1 + + DN objects can add RDN's objects via in-place addition. + + dn1 += dn2 # dn2 RDN's are appended to the dn1's RDN's + dn1 += rdn1 # dn1 has rdn appended to its RDN's + dn1 += "dc=redhat.com" # string is converted to DN, then appended + + The str method of an DN returns the string representation in RFC 4514 DN + syntax with proper escaping. + ''' + + is_mutable = False + flags = 0 + AVA_type = AVA + RDN_type = RDN + + def __init__(self, *args, **kwds): + self.rdns = self._rdns_from_sequence(args) + + def _rdn_from_value(self, value): + if isinstance(value, RDN): + return self.RDN_type(value) + elif isinstance(value, DN): + rdns = [] + for rdn in value.rdns: + rdns.append(self.RDN_type(rdn)) + if len(rdns) == 1: + return rdns[0] + else: + return rdns + elif isinstance(value, basestring): + rdns = [] + try: + dn_list = str2dn(value.encode('utf-8')) + for rdn_list in dn_list: + avas = [] + for ava_tuple in rdn_list: + avas.append(self.AVA_type(ava_tuple[0], ava_tuple[1])) + rdn = self.RDN_type(*avas) + rdns.append(rdn) + except DECODING_ERROR: + raise ValueError("malformed RDN string = \"%s\"" % value) + if len(rdns) == 1: + return rdns[0] + else: + return rdns + elif isinstance(value, (tuple, list)): + if len(value) != 2: + raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (value)) + rdn = self.RDN_type(value) + return rdn + else: + raise TypeError("must be str,unicode,tuple, or RDN, got %s instead" % \ + value.__class__.__name__) + + def _rdns_from_sequence(self, seq): + rdns = [] + + for item in seq: + rdn = self._rdn_from_value(item) + if isinstance(rdn, list): + rdns.extend(rdn) + else: + rdns.append(rdn) + return rdns + + def _to_openldap(self): + return [[(ava.attr.encode('utf-8'), ava.value.encode('utf-8'), self.flags) for ava in rdn] for rdn in self.rdns] + + def __str__(self): + return dn2str(self._to_openldap()) + + def __repr__(self): + return "%s.%s('%s')" % (self.__module__, self.__class__.__name__, self.__str__()) + + def _next(self): + for rdn in self.rdns: + yield rdn + + def __iter__(self): + return self._next() + + def __len__(self): + return len(self.rdns) + + def __getitem__(self, key): + if isinstance(key, (int, long, slice)): + return self.rdns[key] + elif isinstance(key, basestring): + for rdn in self.rdns: + if key == rdn.attr: + return rdn.value + raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) + else: + raise TypeError("unsupported type for DN indexing, must be int, basestring or slice; not %s" % \ + (key.__class__.__name__)) + + def __hash__(self): + # Hash is computed from DN's string representation because it's immutable + return hash(str(self)) + + def __eq__(self, other): + # Try coercing string to DN, if successful compare to coerced object + if isinstance(other, basestring): + try: + other_dn = DN(other) + return self.__eq__(other_dn) + except Exception: + return False + + # If it's not an DN it can't be equal + if not isinstance(other, DN): + return False + + # Perform comparision between objects of same type + return self.rdns == other.rdns + + def __ne__(self, other): + return not self.__eq__(other) + + def __cmp__(self, other): + if not isinstance(other, DN): + raise TypeError("expected DN but got %s" % (other.__class__.__name__)) + + result = cmp(len(self), len(other)) + if result != 0: + return result + return self._cmp_sequence(other, 0, len(self)) + + def _cmp_sequence(self, pattern, self_start, pat_len): + self_idx = self_start + pat_idx = 0 + while pat_idx < pat_len: + result = cmp(self[self_idx], pattern[pat_idx]) + if result != 0: + return result + self_idx += 1 + pat_idx += 1 + return 0 + + def __add__(self, other): + result = self.__class__(self) + if isinstance(other, DN): + for rdn in other.rdns: + result.rdns.append(self.RDN_type(rdn)) + elif isinstance(other, RDN): + result.rdns.append(self.RDN_type(other)) + elif isinstance(other, basestring): + dn = self.__class__(other) + for rdn in dn.rdns: + result.rdns.append(rdn) + else: + raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__)) + + return result + + # The implementation of startswith, endswith, tailmatch, adjust_indices + # was based on the Python's stringobject.c implementation + + def startswith(self, prefix, start=0, end=sys.maxsize): + ''' + Return True if the dn starts with the specified prefix (either a DN or + RDN object), False otherwise. With optional start, test dn beginning at + that position. With optional end, stop comparing dn at that position. + prefix can also be a tuple of dn's or rdn's to try. + ''' + if isinstance(prefix, tuple): + for pat in prefix: + if self._tailmatch(pat, start, end, -1): + return True + return False + + return self._tailmatch(prefix, start, end, -1) + + def endswith(self, suffix, start=0, end=sys.maxsize): + ''' + Return True if dn ends with the specified suffix (either a DN or RDN + object), False otherwise. With optional start, test dn beginning at + that position. With optional end, stop comparing dn at that position. + suffix can also be a tuple of dn's or rdn's to try. + ''' + if isinstance(suffix, tuple): + for pat in suffix: + if self._tailmatch(pat, start, end, +1): + return True + return False + + return self._tailmatch(suffix, start, end, +1) + + def _tailmatch(self, pattern, start, end, direction): + ''' + Matches the end (direction >= 0) or start (direction < 0) of self + against pattern (either a DN or RDN), using the start and end + arguments. Returns 0 if not found and 1 if found. + ''' + + if isinstance(pattern, DN): + pat_len = len(pattern) + elif isinstance(pattern, RDN): + pat_len = 1 + else: + raise TypeError("expected DN or RDN but got %s" % (pattern.__class__.__name__)) + + self_len = len(self) + + start, end = _adjust_indices(start, end, self_len) + + if direction < 0: # starswith + if start+pat_len > self_len: + return 0 + else: # endswith + if end-start < pat_len or start > self_len: + return 0 + + if end-pat_len >= start: + start = end - pat_len + + if isinstance(pattern, DN): + if end-start >= pat_len: + return not self._cmp_sequence(pattern, start, pat_len) + return 0 + else: + return self.rdns[start] == pattern + + def __contains__(self, other): + 'Return the outcome of the test other in self. Note the reversed operands.' + + if isinstance(other, DN): + other_len = len(other) + end = len(self) - other_len + i = 0 + while i <= end: + result = self._cmp_sequence(other, i, other_len) + if result == 0: + return True + i += 1 + return False + + elif isinstance(other, RDN): + return other in self.rdns + else: + raise TypeError("expected DN or RDN but got %s" % (other.__class__.__name__)) + + + def find(self, pattern, start=None, end=None): + ''' + Return the lowest index in the DN where pattern DN (or RDN) is found, + such that pattern is contained in the range [start, end]. Optional + arguments start and end are interpreted as in slice notation. Return + -1 if pattern is not found. + ''' + + if isinstance(pattern, DN): + pat_len = len(pattern) + elif isinstance(pattern, RDN): + pat_len = 1 + else: + raise TypeError("expected DN or RDN but got %s" % (pattern.__class__.__name__)) + + self_len = len(self) + + if start is None: + start = 0 + if end is None: + end = self_len + + start, end = _adjust_indices(start, end, self_len) + + i = start + stop = max(start, end - pat_len) + if isinstance(pattern, DN): + while i <= stop: + result = self._cmp_sequence(pattern, i, pat_len) + if result == 0: + return i + i += 1 + return -1 + else: + while i <= stop: + if self.rdns[i] == pattern: + return i + i += 1 + return -1 + + def index(self, pattern, start=None, end=None): + ''' + Like find() but raise ValueError when the pattern is not found. + ''' + + i = self.find(pattern, start, end) + if i == -1: + raise ValueError("pattern not found") + return i + + def rfind(self, pattern, start=None, end=None): + ''' + Return the highest index in the DN where pattern DN (or RDN) is found, + such that pattern is contained in the range [start, end]. Optional + arguments start and end are interpreted as in slice notation. Return + -1 if pattern is not found. + ''' + + if isinstance(pattern, DN): + pat_len = len(pattern) + elif isinstance(pattern, RDN): + pat_len = 1 + else: + raise TypeError("expected DN or RDN but got %s" % (pattern.__class__.__name__)) + + self_len = len(self) + + if start is None: + start = 0 + if end is None: + end = self_len + + start, end = _adjust_indices(start, end, self_len) + + i = max(start, min(end, self_len - pat_len)) + stop = start + if isinstance(pattern, DN): + while i >= stop: + result = self._cmp_sequence(pattern, i, pat_len) + if result == 0: + return i + i -= 1 + return -1 + else: + while i >= stop: + if self.rdns[i] == pattern: + return i + i -= 1 + return -1 + + def rindex(self, pattern, start=None, end=None): + ''' + Like rfind() but raise ValueError when the pattern is not found. + ''' + + i = self.rfind(pattern, start, end) + if i == -1: + raise ValueError("pattern not found") + return i + +class EditableDN(DN): + ''' + Exactly identical to the DN class except + + * Hash value is based on object identity, not object + value. Objects that test as equal will be non-unique when + used as a dict key or member of a set. + + * RDN components may be assigned via assignment statements. + + * RDN components may be inserted. + + * In-place addition modifes the lhs object. + + ''' + + is_mutable = True + __hash__ = None + AVA_type = EditableAVA + RDN_type = EditableRDN + + def __setitem__(self, key, value): + if isinstance(key, (int, long)): + new_rdn = self._rdn_from_value(value) + if isinstance(new_rdn, list): + raise TypeError("cannot assign multiple RDN's to single entry") + self.rdns[key] = new_rdn + elif isinstance(key, slice): + rdns = self._rdns_from_sequence(value) + self.rdns[key] = rdns + elif isinstance(key, basestring): + new_rdn = self._rdn_from_value(value) + if isinstance(new_rdn, list): + raise TypeError("cannot assign multiple values to single entry") + found = False + i = 0 + while i < len(self.rdns): + if key == self.rdns[i].attr: + found = True + self.rdns[i] = new_rdn + break + i += 1 + if not found: + raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) + else: + raise TypeError("unsupported type for DN indexing, must be int, basestring or slice; not %s" % \ + (key.__class__.__name__)) + + def __iadd__(self, other): + # If __iadd__ is not available Python will emulate += by + # replacing the lhs object with the result of __add__ (if available). + if isinstance(other, DN): + for rdn in other.rdns: + self.rdns.append(self.RDN_type(rdn)) + elif isinstance(other, RDN): + self.rdns.append(self.RDN_type(other)) + elif isinstance(other, basestring): + dn = self.__class__(other) + self.__iadd__(dn) + else: + raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__)) + + return self + + def insert(self, i, x): + ''' + x must be a 2-value tuple or list promotable to an RDN object, + or a RDN object. + + dn.insert(i, x) is the same as s[i:i] = [x] + + When a negative index is passed as the first parameter to the + insert() method, the list length is added, as for slice + indices. If it is still negative, it is truncated to zero, as + for slice indices. + ''' + + self.rdns.insert(i, self._rdn_from_value(x)) + + def replace(self, old, new, count=sys.maxsize): + ''' + Replace all occurrences of old DN (or RDN) with new DN (or + RDN). If the optional argument count is given, only the first + count occurrences are replaced. + + Returns the number of replacements made. + ''' + + if not isinstance(old, (DN, RDN)): + raise TypeError("old must be DN or RDN but got %s" % (old.__class__.__name__)) + if not isinstance(new, (DN, RDN)): + raise TypeError("new must be DN or RDN but got %s" % (new.__class__.__name__)) + + + start = 0 + pat_len = len(old) + n_replaced = 0 + while n_replaced < count: + index = self.find(old, start) + if index < 0: + return n_replaced + self[index : index+pat_len] = new + n_replaced += 1 + start = index + pat_len + + return n_replaced diff --git a/ipapython/entity.py b/ipapython/entity.py index 27d517879..cb97b9913 100644 --- a/ipapython/entity.py +++ b/ipapython/entity.py @@ -18,6 +18,7 @@ import copy from ipapython import ipautil +from ipapython.dn import DN def copy_CIDict(x): """Do a deep copy of a CIDict""" @@ -45,19 +46,25 @@ class Entity: if isinstance(entrydata,tuple): self.dn = entrydata[0] self.data = ipautil.CIDict(entrydata[1]) - elif isinstance(entrydata,str) or isinstance(entrydata,unicode): + elif isinstance(entrydata, DN): self.dn = entrydata self.data = ipautil.CIDict() + elif isinstance(entrydata, basestring): + self.dn = DN(entrydata) + self.data = ipautil.CIDict() elif isinstance(entrydata,dict): self.dn = entrydata['dn'] del entrydata['dn'] self.data = ipautil.CIDict(entrydata) else: - self.dn = '' + self.dn = DN() self.data = ipautil.CIDict() + assert isinstance(self.dn, DN) self.orig_data = ipautil.CIDict(copy_CIDict(self.data)) + dn = ipautil.dn_attribute_property('_dn') + def __nonzero__(self): """This allows us to do tests like if entry: returns false if there is no data, true otherwise""" @@ -67,23 +74,8 @@ class Entity: """Return True if this entry has an attribute named name, False otherwise""" return self.data and self.data.has_key(name) - def __setattr__(self,name,value): - """One should use setValue() or setValues() to set values except for - dn and data which are special.""" - if name != 'dn' and name != 'data' and name != 'orig_data': - raise KeyError, 'use setValue() or setValues()' - else: - self.__dict__[name] = value - - def __getattr__(self,name): - """If name is the name of an LDAP attribute, return the first value for that - attribute - equivalent to getValue - this allows the use of - entry.cn - instead of - entry.getValue('cn') - This also allows us to return None if an attribute is not found rather than - throwing an exception""" - return self.getValue(name) + def __str__(self): + return "dn: %s data: %s" % (self.dn, self.data) def getValues(self,name): """Get the list (array) of values for the attribute named name""" @@ -150,6 +142,7 @@ class Entity: def toDict(self): """Convert the attrs and values to a dict. The dict is keyed on the attribute name. The value is either single value or a list of values.""" + assert isinstance(self.dn, DN) result = ipautil.CIDict(self.data) result['dn'] = self.dn return result @@ -160,6 +153,7 @@ class Entity: def origDataDict(self): """Returns a dict of the original values of the user. Used for updates.""" + assert isinstance(self.dn, DN) result = ipautil.CIDict(self.orig_data) result['dn'] = self.dn return result diff --git a/ipapython/ipa_log_manager.py b/ipapython/ipa_log_manager.py index 8af0f79a7..f2cd4d3a1 100644 --- a/ipapython/ipa_log_manager.py +++ b/ipapython/ipa_log_manager.py @@ -101,10 +101,10 @@ class IPALogManager(LogManager): in the Env config must begin with "log_logger_level\_" and then be followed by a symbolic or numeric log level, for example:: - log_logger_level_debug = ipalib\.dn\..* + log_logger_level_debug = ipapython\.dn\..* log_logger_level_35 = ipalib\.plugins\.dogtag - The first line says any logger belonging to the ipalib.dn module + The first line says any logger belonging to the ipapython.dn module will have it's level configured to debug. The second line say the ipa.plugins.dogtag logger will be diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index 1b0597763..a212aa6ef 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -49,6 +49,7 @@ from dns.exception import DNSException from ipapython.ipa_log_manager import * from ipapython import ipavalidate from ipapython import config +from ipapython.dn import DN try: from subprocess import CalledProcessError @@ -200,10 +201,16 @@ def format_netloc(host, port=None): return '%s:%s' % (host, str(port)) def realm_to_suffix(realm_name): - """Convert a kerberos realm into the IPA suffix.""" + 'Convert a kerberos realm to a IPA suffix.' s = realm_name.split(".") - terms = ["dc=" + x.lower() for x in s] - return ",".join(terms) + suffix_dn = DN(*[('dc', x.lower()) for x in s]) + return suffix_dn + +def suffix_to_realm(suffix_dn): + 'Convert a IPA suffix to a kerberos realm.' + assert isinstance(suffix_dn, DN) + realm = '.'.join([x.value for x in suffix_dn]) + return realm def template_str(txt, vars): val = string.Template(txt).substitute(vars) @@ -1111,3 +1118,33 @@ def kinit_hostprincipal(keytab, ccachedir, principal): return ccache_file except krbV.Krb5Error, e: raise StandardError('Error initializing principal %s in %s: %s' % (principal, keytab, str(e))) + +def dn_attribute_property(private_name): + ''' + Create a property for a dn attribute which assures the attribute + is a DN or None. If the value is not None the setter converts it to + a DN. The getter assures it's either None or a DN instance. + + The private_name parameter is the class internal attribute the property + shadows. + + For example if a class has an attribute called base_dn, then: + + base_dn = dn_attribute_property('_base_dn') + + Thus the class with have an attriubte called base_dn which can only + ever be None or a DN instance. The actual value is stored in _base_dn. + ''' + + def setter(self, value): + if value is not None: + value = DN(value) + setattr(self, private_name, value) + + def getter(self): + value = getattr(self, private_name) + if value is not None: + assert isinstance(value, DN) + return value + + return property(getter, setter) diff --git a/ipaserver/conn.py b/ipaserver/conn.py deleted file mode 100644 index 070a6adc8..000000000 --- a/ipaserver/conn.py +++ /dev/null @@ -1,69 +0,0 @@ -# Authors: Rob Crittenden -# -# Copyright (C) 2008 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 . -# - -import krbV -import ldap -import ldap.dn -import ipaldap - -class IPAConn: - def __init__(self, host, port, krbccache, debug=None): - self._conn = None - - # Save the arguments - self._host = host - self._port = port - self._krbccache = krbccache - self._debug = debug - - self._ctx = krbV.default_context() - - ccache = krbV.CCache(name=krbccache, context=self._ctx) - cprinc = ccache.principal() - - self._conn = ipaldap.IPAdmin(host,port,None,None,None,debug) - - # This will bind the connection - try: - self._conn.set_krbccache(krbccache, cprinc.name) - except ldap.UNWILLING_TO_PERFORM, e: - raise e - except Exception, e: - raise e - - def __del__(self): - # take no chances on unreleased connections - self.releaseConn() - - def getConn(self): - return self._conn - - def releaseConn(self): - if self._conn is None: - return - - self._conn.unbind_s() - self._conn = None - - return - -if __name__ == "__main__": - ipaconn = IPAConn("localhost", 389, "FILE:/tmp/krb5cc_500") - x = ipaconn.getConn().getEntry("dc=example,dc=com", ldap.SCOPE_SUBTREE, "uid=admin", ["cn"]) - print "%s" % x diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py index 982e12b31..f7abf62e4 100644 --- a/ipaserver/dcerpc.py +++ b/ipaserver/dcerpc.py @@ -101,7 +101,7 @@ class DomainValidator(object): def is_configured(self): cn_trust_local = DN(('cn', self.api.env.domain), self.api.env.container_cifsdomains, self.api.env.basedn) try: - (dn, entry_attrs) = self.ldap.get_entry(unicode(cn_trust_local), [self.ATTR_FLATNAME, self.ATTR_SID]) + (dn, entry_attrs) = self.ldap.get_entry(cn_trust_local, [self.ATTR_FLATNAME, self.ATTR_SID]) self.flatname = entry_attrs[self.ATTR_FLATNAME][0] self.sid = entry_attrs[self.ATTR_SID][0] self.dn = dn @@ -115,7 +115,7 @@ class DomainValidator(object): try: search_kw = {'objectClass': 'ipaNTTrustedDomain'} filter = self.ldap.make_filter(search_kw, rules=self.ldap.MATCH_ALL) - (entries, truncated) = self.ldap.find_entries(filter=filter, base_dn=unicode(cn_trust), + (entries, truncated) = self.ldap.find_entries(filter=filter, base_dn=cn_trust, attrs_list=[self.ATTR_TRUSTED_SID, 'dn']) return entries @@ -447,5 +447,3 @@ class TrustDomainJoins(object): self.__populate_remote_domain(realm, realm_server, realm_passwd=None) self.local_domain.establish_trust(self.remote_domain, trustdom_passwd) return dict(local=self.local_domain, remote=self.remote_domain) - - diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py index 9dcbec2d6..078c54dbe 100644 --- a/ipaserver/install/adtrustinstance.py +++ b/ipaserver/install/adtrustinstance.py @@ -29,11 +29,12 @@ from ipaserver.install.dsinstance import realm_to_serverid from ipaserver.install.bindinstance import get_rr, add_rr, del_rr, \ dns_zone_exists from ipalib import errors, api -from ipalib.dn import DN +from ipapython.dn import DN from ipapython import sysrestore from ipapython import ipautil from ipapython.ipa_log_manager import * from ipapython import services as ipaservices +from ipapython.dn import DN import string import struct @@ -103,7 +104,7 @@ class ADTRUSTInstance(service.Service): self.netbios_name = None self.no_msdcs = None self.smbd_user = None - self.suffix = None + self.suffix = DN() self.ldapi_socket = None self.smb_conf = None self.smb_dn = None @@ -129,11 +130,10 @@ class ADTRUSTInstance(service.Service): return "S-1-5-21-%d-%d-%d" % (sub_ids[0], sub_ids[1], sub_ids[2]) def __add_admin_sids(self): - admin_dn = str(DN(('uid', 'admin'), api.env.container_user, - self.suffix)) - admin_group_dn = str(DN(('cn', 'admins'), api.env.container_group, - self.suffix)) - + admin_dn = DN(('uid', 'admin'), api.env.container_user, + self.suffix) + admin_group_dn = DN(('cn', 'admins'), api.env.container_group, + self.suffix) try: dom_entry = self.admin_conn.getEntry(self.smb_dom_dn, \ ldap.SCOPE_BASE) @@ -186,10 +186,9 @@ class ADTRUSTInstance(service.Service): """ try: - res = self.admin_conn.search_s(str(DN(api.env.container_ranges, - self.suffix)), - ldap.SCOPE_ONELEVEL, - "(objectclass=ipaDomainIDRange)") + res = self.admin_conn.getList(DN(api.env.container_ranges, self.suffix), + ldap.SCOPE_ONELEVEL, + "(objectclass=ipaDomainIDRange)") if len(res) != 1: root_logger.critical("Found more than one ID range for the " \ "local domain.") @@ -230,17 +229,18 @@ class ADTRUSTInstance(service.Service): pass for new_dn in (self.trust_dn, \ - str(DN(('cn', 'ad'), self.trust_dn)), \ - str(DN(api.env.container_cifsdomains, self.suffix))): + DN(('cn', 'ad'), self.trust_dn), \ + DN(api.env.container_cifsdomains, self.suffix)): try: self.admin_conn.getEntry(new_dn, ldap.SCOPE_BASE) except errors.NotFound: entry = ipaldap.Entry(new_dn) entry.setValues("objectclass", ["nsContainer"]) - name = new_dn.split('=')[1].split(',')[0] - if not name: - print "Cannot extract RDN attribute value from [%s]" % \ - new_dn + try: + name = new_dn[1].attr + except Exception, e: + print 'Cannot extract RDN attribute value from "%s": %s' % \ + (new_dn, e) return entry.setValues("cn", name) self.admin_conn.addEntry(entry) @@ -474,16 +474,16 @@ class ADTRUSTInstance(service.Service): self.smb_conf = "/etc/samba/smb.conf" - self.smb_dn = str(DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), - ('cn', 'etc'), self.suffix)) + self.smb_dn = DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), + ('cn', 'etc'), self.suffix) - self.trust_dn = str(DN(api.env.container_trusts, self.suffix)) - self.smb_dom_dn = str(DN(('cn', self.domain_name), - api.env.container_cifsdomains, self.suffix)) + self.trust_dn = DN(api.env.container_trusts, self.suffix) + self.smb_dom_dn = DN(('cn', self.domain_name), + api.env.container_cifsdomains, self.suffix) self.cifs_principal = "cifs/" + self.fqdn + "@" + self.realm - self.cifs_agent = str(DN(('krbprincipalname', self.cifs_principal.lower()), - api.env.container_service, - self.suffix)) + self.cifs_agent = DN(('krbprincipalname', self.cifs_principal.lower()), + api.env.container_service, + self.suffix) self.selinux_booleans = ["samba_portmapper"] self.__setup_sub_dict() @@ -491,16 +491,13 @@ class ADTRUSTInstance(service.Service): def find_local_id_range(self): self.ldap_connect() - if self.admin_conn.search_s(str(DN(api.env.container_ranges, - self.suffix)), + if self.admin_conn.search_s(DN(api.env.container_ranges, self.suffix), ldap.SCOPE_ONELEVEL, "objectclass=ipaDomainIDRange"): return try: - entry = self.admin_conn.getEntry(str(DN(('cn', 'admins'), - api.env.container_group, - self.suffix)), + entry = self.admin_conn.getEntry(DN(('cn', 'admins'), api.env.container_group, self.suffix), ldap.SCOPE_BASE) except errors.NotFound: raise ValueError("No local ID range and no admins group found.\n" \ @@ -523,9 +520,9 @@ class ADTRUSTInstance(service.Service): "range.\nAdd local ID range manually and try " \ "again!") - entry = ipaldap.Entry(str(DN(('cn', ('%s_id_range' % self.realm)), - api.env.container_ranges, - self.suffix))) + entry = ipaldap.Entry(DN(('cn', ('%s_id_range' % self.realm)), + api.env.container_ranges, + self.suffix)) entry.setValue('objectclass', 'ipaDomainIDRange') entry.setValue('cn', ('%s_id_range' % self.realm)) entry.setValue('ipaBaseID', str(base_id)) diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index f320202ea..2e00f70b1 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -38,6 +38,7 @@ from ipapython.ipa_log_manager import * import ipalib from ipalib import api, util, errors +from ipapython.dn import DN NAMED_CONF = '/etc/named.conf' RESOLV_CONF = '/etc/resolv.conf' @@ -166,10 +167,11 @@ def dns_container_exists(fqdn, suffix, dm_password=None, ldapi=False, realm=None Test whether the dns container exists. """ - def object_exists(dn): + def object_exists(dn): # FIXME, this should be a IPAdmin/ldap2 method so it can be shared """ Test whether the given object exists in LDAP. """ + assert isinstance(dn, DN) try: conn.search_ext_s(dn, ldap.SCOPE_BASE) except ldap.NO_SUCH_OBJECT: @@ -177,6 +179,7 @@ def dns_container_exists(fqdn, suffix, dm_password=None, ldapi=False, realm=None else: return True + assert isinstance(suffix, DN) try: # At install time we may need to use LDAPI to avoid chicken/egg # issues with SSL certs and truting CAs @@ -192,7 +195,7 @@ def dns_container_exists(fqdn, suffix, dm_password=None, ldapi=False, realm=None except ldap.SERVER_DOWN: raise RuntimeError('LDAP server on %s is not responding. Is IPA installed?' % fqdn) - ret = object_exists("cn=dns,%s" % suffix) + ret = object_exists(DN(('cn', 'dns'), suffix)) conn.unbind_s() return ret @@ -288,11 +291,14 @@ def add_zone(name, zonemgr=None, dns_backup=None, ns_hostname=None, ns_ip_addres ns_main = ns_hostname ns_replicas = [] + if ns_ip_address is not None: + ns_ip_address = unicode(ns_ip_address) + try: api.Command.dnszone_add(unicode(name), idnssoamname=unicode(ns_main+'.'), idnssoarname=unicode(zonemgr), - ip_address=unicode(ns_ip_address), + ip_address=ns_ip_address, idnsallowdynupdate=True, idnsupdatepolicy=unicode(update_policy), idnsallowquery=u'any', @@ -329,11 +335,14 @@ def add_reverse_zone(zone, ns_hostname=None, ns_ip_address=None, ns_main = ns_hostname ns_replicas = [] + if ns_ip_address is not None: + ns_ip_address = unicode(ns_ip_address) + try: api.Command.dnszone_add(unicode(zone), idnssoamname=unicode(ns_main+'.'), idnsallowdynupdate=True, - ip_address=unicode(ns_ip_address), + ip_address=ns_ip_address, idnsupdatepolicy=unicode(update_policy), idnsallowquery=u'any', idnsallowtransfer=u'none',) @@ -465,6 +474,8 @@ class BindInstance(service.Service): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + suffix = ipautil.dn_attribute_property('_suffix') + def setup(self, fqdn, ip_address, realm_name, domain_name, forwarders, ntp, reverse_zone, named_user="named", zonemgr=None, zone_refresh=0, persistent_search=True, serial_autoincrement=True): @@ -574,7 +585,7 @@ class BindInstance(service.Service): if self.ntp: optional_ntp = "\n;ntp server\n" - optional_ntp += "_ntp._udp\t\tIN SRV 0 100 123\t%s""" % self.host_in_rr + optional_ntp += "_ntp._udp\t\tIN SRV 0 100 123\t%s" % self.host_in_rr else: optional_ntp = "" @@ -654,7 +665,8 @@ class BindInstance(service.Service): p = self.move_service(dns_principal) if p is None: # the service has already been moved, perhaps we're doing a DNS reinstall - dns_principal = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (dns_principal, self.suffix) + dns_principal = DN(('krbprincipalname', dns_principal), + ('cn', 'services'), ('cn', 'accounts'), self.suffix) else: dns_principal = p @@ -667,9 +679,7 @@ class BindInstance(service.Service): # it can host the memberof attribute, then also add it to the # dnsserver role group, this way the DNS is allowed to perform # DNS Updates - dns_group = "cn=DNS Servers,cn=privileges,cn=pbac,%s" % self.suffix - if isinstance(dns_principal, unicode): - dns_principal = dns_principal.encode('utf-8') + dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), self.suffix) mod = [(ldap.MOD_ADD, 'member', dns_principal)] try: diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index dc4374cce..b00ceeaed 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -39,7 +39,7 @@ from ipapython import dogtag from ipapython.certdb import get_ca_nickname from ipapython import certmonger from ipalib import pkcs10, x509 -from ipalib.dn import DN +from ipapython.dn import DN import subprocess from nss.error import NSPRError @@ -243,7 +243,9 @@ class CADSInstance(service.Service): self.suffix = ipautil.realm_to_suffix(self.realm_name) self.__setup_sub_dict() else: - self.suffix = None + self.suffix = DN() + + subject_base = ipautil.dn_attribute_property('_subject_base') def create_instance(self, realm_name, host_name, domain_name, dm_password, pkcs12_info=None, ds_port=DEFAULT_DSPORT, @@ -268,7 +270,7 @@ class CADSInstance(service.Service): def __setup_sub_dict(self): server_root = dsinstance.find_server_root() self.sub_dict = dict(FQDN=self.fqdn, SERVERID=self.serverid, - PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(), + PASSWORD=self.dm_password, SUFFIX=self.suffix, REALM=self.realm_name, USER=PKI_DS_USER, SERVER_ROOT=server_root, DOMAIN=self.domain, TIME=int(time.time()), DSPORT=self.ds_port, @@ -342,7 +344,7 @@ class CADSInstance(service.Service): def enable_ssl(self): conn = ipaldap.IPAdmin("127.0.0.1", port=DEFAULT_DSPORT) - conn.simple_bind_s("cn=directory manager", self.dm_password) + conn.simple_bind_s(DN(('cn', 'directory manager')), self.dm_password) mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"), (ldap.MOD_REPLACE, "nsSSL3Ciphers", @@ -350,13 +352,13 @@ class CADSInstance(service.Service): +rsa_des_sha,+rsa_fips_des_sha,+rsa_3des_sha,+rsa_fips_3des_sha,+fortezza,\ +fortezza_rc4_128_sha,+fortezza_null,+tls_rsa_export1024_with_rc4_56_sha,\ +tls_rsa_export1024_with_des_cbc_sha")] - conn.modify_s("cn=encryption,cn=config", mod) + conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod) mod = [(ldap.MOD_ADD, "nsslapd-security", "on"), (ldap.MOD_ADD, "nsslapd-secureport", str(DEFAULT_DSPORT+1))] - conn.modify_s("cn=config", mod) + conn.modify_s(DN(('cn', 'config')), mod) - entry = ipaldap.Entry("cn=RSA,cn=encryption,cn=config") + entry = ipaldap.Entry(DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsEncryptionModule") entry.setValues("cn", "RSA") @@ -460,7 +462,7 @@ class CAInstance(service.Service): # will already have been initialized by Apache by the time # mod_python wants to do things. self.canickname = get_ca_nickname(realm) - self.basedn = "o=ipaca" + self.basedn = DN(('o', 'ipaca')) self.ca_agent_db = tempfile.mkdtemp(prefix = "tmp-") self.ra_agent_db = ra_db self.ra_agent_pwd = self.ra_agent_db + "/pwdfile.txt" @@ -506,7 +508,7 @@ class CAInstance(service.Service): self.clone = True self.master_host = master_host if subject_base is None: - self.subject_base = "O=%s" % self.realm + self.subject_base = DN(('O', self.realm)) else: self.subject_base = subject_base @@ -615,12 +617,12 @@ class CAInstance(service.Service): "-agent_name", "ipa-ca-agent", "-agent_key_size", "2048", "-agent_key_type", "rsa", - "-agent_cert_subject", "CN=ipa-ca-agent,%s" % self.subject_base, + "-agent_cert_subject", str(DN(('CN', 'ipa-ca-agent'), self.subject_base)), "-ldap_host", self.fqdn, "-ldap_port", str(self.ds_port), "-bind_dn", "cn=Directory Manager", "-bind_password", self.dm_password, - "-base_dn", self.basedn, + "-base_dn", str(self.basedn), "-db_name", "ipaca", "-key_size", "2048", "-key_type", "rsa", @@ -629,11 +631,12 @@ class CAInstance(service.Service): "-backup_pwd", self.admin_password, "-subsystem_name", self.service_name, "-token_name", "internal", - "-ca_subsystem_cert_subject_name", "CN=CA Subsystem,%s" % self.subject_base, - "-ca_ocsp_cert_subject_name", "CN=OCSP Subsystem,%s" % self.subject_base, - "-ca_server_cert_subject_name", "CN=%s,%s" % (self.fqdn, self.subject_base), - "-ca_audit_signing_cert_subject_name", "CN=CA Audit,%s" % self.subject_base, - "-ca_sign_cert_subject_name", "CN=Certificate Authority,%s" % self.subject_base ] + "-ca_subsystem_cert_subject_name", str(DN(('CN', 'CA Subsystem'), self.subject_base)), + "-ca_subsystem_cert_subject_name", str(DN(('CN', 'CA Subsystem'), self.subject_base)), + "-ca_ocsp_cert_subject_name", str(DN(('CN', 'OCSP Subsystem'), self.subject_base)), + "-ca_server_cert_subject_name", str(DN(('CN', self.fqdn), self.subject_base)), + "-ca_audit_signing_cert_subject_name", str(DN(('CN', 'CA Audit'), self.subject_base)), + "-ca_sign_cert_subject_name", str(DN(('CN', 'Certificate Authority'), self.subject_base)) ] if self.external == 1: args.append("-external") args.append("true") @@ -836,13 +839,12 @@ class CAInstance(service.Service): # Create an RA user in the CA LDAP server and add that user to # the appropriate groups so it can issue certificates without # manual intervention. - ld = ldap.initialize("ldap://%s" % ipautil.format_netloc(self.fqdn, self.ds_port)) - ld.protocol_version=ldap.VERSION3 - ld.simple_bind_s("cn=Directory Manager", self.dm_password) + conn = ipaldap.IPAdmin(self.fqdn, self.ds_port) + conn.simple_bind_s(DN(('cn', 'Directory Manager')), self.dm_password) decoded = base64.b64decode(self.ra_cert) - entry_dn = "uid=%s,ou=People,%s" % ("ipara", self.basedn) + entry_dn = DN(('uid', "ipara"), ('ou', 'People'), self.basedn) entry = [ ('objectClass', ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'cmsuser']), ('uid', "ipara"), @@ -851,19 +853,23 @@ class CAInstance(service.Service): ('usertype', "agentType"), ('userstate', "1"), ('userCertificate', decoded), - ('description', '2;%s;CN=Certificate Authority,%s;CN=IPA RA,%s' % (str(self.requestId), self.subject_base, self.subject_base)),] + ('description', '2;%s;%s;%s' % \ + (str(self.requestId), + DN(('CN', 'Certificate Authority'), self.subject_base), + DN(('CN', 'IPA RA'), self.subject_base))), + ] - ld.add_s(entry_dn, entry) + conn.add_s(entry_dn, entry) - dn = "cn=Certificate Manager Agents,ou=groups,%s" % self.basedn + dn = DN(('cn', 'Certificate Manager Agents'), ('ou', 'groups'), self.basedn) modlist = [(0, 'uniqueMember', '%s' % entry_dn)] - ld.modify_s(dn, modlist) + conn.modify_s(dn, modlist) - dn = "cn=Registration Manager Agents,ou=groups,%s" % self.basedn + dn = DN(('cn', 'Registration Manager Agents'), ('ou', 'groups'), self.basedn) modlist = [(0, 'uniqueMember', '%s' % entry_dn)] - ld.modify_s(dn, modlist) + conn.modify_s(dn, modlist) - ld.unbind_s() + conn.unbind_s() def __run_certutil(self, args, database=None, pwd_file=None,stdin=None): if not database: @@ -969,7 +975,7 @@ class CAInstance(service.Service): # Generate our CSR. The result gets put into stdout try: - (stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", "CN=IPA RA,%s" % self.subject_base, "-z", noise_name, "-a"]) + (stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", str(DN(('CN', 'IPA RA'), self.subject_base)), "-z", noise_name, "-a"]) finally: os.remove(noise_name) @@ -1071,7 +1077,7 @@ class CAInstance(service.Service): def __set_subject_in_config(self): # dogtag ships with an IPA-specific profile that forces a subject # format. We need to update that template with our base subject - if installutils.update_file(IPA_SERVICE_PROFILE, 'OU=pki-ipa, O=IPA', self.subject_base): + if installutils.update_file(IPA_SERVICE_PROFILE, 'OU=pki-ipa, O=IPA', str(self.subject_base)): print "Updating subject_base in CA template failed" def uninstall(self): diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index d25a471ea..eebaa48c4 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -39,7 +39,7 @@ from ipalib import pkcs10 from ConfigParser import RawConfigParser, MissingSectionHeaderError from ipapython import services as ipaservices from ipalib import x509 -from ipalib.dn import DN +from ipapython.dn import DN from ipalib.errors import CertificateOperationError from nss.error import NSPRError @@ -224,8 +224,7 @@ class CertDB(object): self.self_signed_ca = ipa_self_signed() if not subject_base: - self.subject_base = "O=IPA" - self.subject_format = "CN=%%s,%s" % self.subject_base + self.subject_base = DN(('O', 'IPA')) self.cacert_name = get_ca_nickname(self.realm) self.valid_months = "120" @@ -245,6 +244,8 @@ class CertDB(object): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + subject_base = ipautil.dn_attribute_property('_subject_base') + def __del__(self): if self.reqdir is not None: shutil.rmtree(self.reqdir, ignore_errors=True) @@ -381,11 +382,11 @@ class CertDB(object): def create_ca_cert(self): os.chdir(self.secdir) - subject = "cn=%s Certificate Authority" % self.realm + subject = DN(('cn', '%s Certificate Authority' % self.realm)) p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, "-S", "-n", self.cacert_name, - "-s", subject, + "-s", str(subject), "-x", "-t", "CT,,C", "-1", @@ -565,7 +566,7 @@ class CertDB(object): if not cdb: cdb = self if subject is None: - subject=self.subject_format % hostname + subject=DN(('CN', hostname), self.subject_base) self.request_cert(subject) cdb.issue_server_cert(self.certreq_fname, self.certder_fname) self.add_cert(self.certder_fname, nickname) @@ -583,7 +584,7 @@ class CertDB(object): if not cdb: cdb = self if subject is None: - subject=self.subject_format % hostname + subject=DN(('CN', hostname), self.subject_base) self.request_cert(subject) cdb.issue_signing_cert(self.certreq_fname, self.certder_fname) self.add_cert(self.certder_fname, nickname) @@ -591,9 +592,10 @@ class CertDB(object): os.unlink(self.certder_fname) def request_cert(self, subject, certtype="rsa", keysize="2048"): + assert isinstance(subject, DN) self.create_noise_file() self.setup_cert_request() - args = ["-R", "-s", subject, + args = ["-R", "-s", str(subject), "-o", self.certreq_fname, "-k", certtype, "-g", keysize, @@ -1046,19 +1048,19 @@ class CertDB(object): # Prepare a simple cert request req_dict = dict(PASSWORD=self.gen_password(), SUBJBASE=self.subject_base, - CERTNAME="CN="+nickname) + CERTNAME=DN(('CN', nickname))) req_template = ipautil.SHARE_DIR + reqcfg + ".template" conf = ipautil.template_file(req_template, req_dict) fd = open(reqcfg, "w+") fd.write(conf) fd.close() - base = self.subject_base.replace(",", "/") - esc_subject = "CN=%s/%s" % (nickname, base) + base = str(self.subject_base).replace(",", "/") + esc_subject = DN(('CN', '%s/%s' % (nickname, base))) ipautil.run(["/usr/bin/openssl", "req", "-new", "-config", reqcfg, - "-subj", esc_subject, + "-subj", str(esc_subject), "-key", key_fname, "-out", "kdc.req"]) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 9f3ae7252..bf6677381 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -36,12 +36,12 @@ import service import installutils import certs import ldap -from ldap.dn import escape_dn_chars from ipaserver import ipaldap from ipaserver.install import ldapupdate from ipaserver.install import httpinstance from ipaserver.install import replication from ipalib import util, errors +from ipapython.dn import DN from ipaserver.plugins.ldap2 import ldap2 SERVER_ROOT_64 = "/usr/lib64/dirsrv" @@ -177,7 +177,7 @@ class DsInstance(service.Service): self.suffix = ipautil.realm_to_suffix(self.realm_name) self.__setup_sub_dict() else: - self.suffix = None + self.suffix = DN() if fstore: self.fstore = fstore @@ -185,6 +185,8 @@ class DsInstance(service.Service): self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + subject_base = ipautil.dn_attribute_property('_subject_base') + def __common_setup(self): self.step("creating directory server user", self.__create_ds_user) @@ -299,7 +301,7 @@ class DsInstance(service.Service): self.fqdn, self.dm_password) repl.setup_replication(self.master_fqdn, - r_binddn="cn=Directory Manager", + r_binddn=DN(('cn', 'Directory Manager')), r_bindpw=self.dm_password) self.run_init_memberof = repl.needs_memberof_fixup() @@ -314,12 +316,12 @@ class DsInstance(service.Service): self.sub_dict = dict(FQDN=self.fqdn, SERVERID=self.serverid, PASSWORD=self.dm_password, RANDOM_PASSWORD=self.generate_random(), - SUFFIX=self.suffix.lower(), + SUFFIX=self.suffix, REALM=self.realm_name, USER=DS_USER, SERVER_ROOT=server_root, DOMAIN=self.domain, TIME=int(time.time()), IDSTART=self.idstart, IDMAX=self.idmax, HOST=self.fqdn, - ESCAPED_SUFFIX= escape_dn_chars(self.suffix.lower()), + ESCAPED_SUFFIX=str(self.suffix), GROUP=DS_GROUP, IDRANGE_SIZE=self.idmax-self.idstart+1 ) @@ -445,11 +447,12 @@ class DsInstance(service.Service): self._ldap_mod("memberof-task.ldif", self.sub_dict) # Note, keep dn in sync with dn in install/share/memberof-task.ldif - dn = "cn=IPA install %s,cn=memberof task,cn=tasks,cn=config" % self.sub_dict["TIME"] + dn = DN(('cn', 'IPA install %s' % self.sub_dict["TIME"]), ('cn', 'memberof task'), + ('cn', 'tasks'), ('cn', 'config')) root_logger.debug("Waiting for memberof task to complete.") conn = ipaldap.IPAdmin("127.0.0.1") if self.dm_password: - conn.simple_bind_s("cn=directory manager", self.dm_password) + conn.simple_bind_s(DN(('cn', 'directory manager')), self.dm_password) else: conn.do_sasl_gssapi_bind() conn.checkTask(dn, dowait=True) @@ -543,7 +546,7 @@ class DsInstance(service.Service): dsdb.create_pin_file() conn = ipaldap.IPAdmin("127.0.0.1") - conn.simple_bind_s("cn=directory manager", self.dm_password) + conn.simple_bind_s(DN(('cn', 'directory manager')), self.dm_password) mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"), (ldap.MOD_REPLACE, "nsSSL3Ciphers", @@ -551,12 +554,12 @@ class DsInstance(service.Service): +rsa_des_sha,+rsa_fips_des_sha,+rsa_3des_sha,+rsa_fips_3des_sha,+fortezza,\ +fortezza_rc4_128_sha,+fortezza_null,+tls_rsa_export1024_with_rc4_56_sha,\ +tls_rsa_export1024_with_des_cbc_sha")] - conn.modify_s("cn=encryption,cn=config", mod) + conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod) mod = [(ldap.MOD_ADD, "nsslapd-security", "on")] - conn.modify_s("cn=config", mod) + conn.modify_s(DN(('cn', 'config')), mod) - entry = ipaldap.Entry("cn=RSA,cn=encryption,cn=config") + entry = ipaldap.Entry(DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsEncryptionModule") entry.setValues("cn", "RSA") @@ -612,9 +615,9 @@ class DsInstance(service.Service): os.close(admpwdfd) args = ["/usr/bin/ldappasswd", "-h", self.fqdn, - "-ZZ", "-x", "-D", "cn=Directory Manager", + "-ZZ", "-x", "-D", str(DN(('cn', 'Directory Manager'))), "-y", dmpwdfile, "-T", admpwdfile, - "uid=admin,cn=users,cn=accounts,"+self.suffix] + str(DN(('uid', 'admin'), ('cn', 'users'), ('cn', 'accounts'), self.suffix))] try: env = { 'LDAPTLS_CACERTDIR':os.path.dirname(CACERT), 'LDAPTLS_CACERT':CACERT } @@ -801,22 +804,19 @@ class DsInstance(service.Service): def replica_populate(self): self.ldap_connect() - dn = "cn=default,ou=profile,%s" % self.suffix + dn = DN(('cn', 'default'), ('ou', 'profile'), self.suffix) try: - ret = self.admin_conn.search_s(dn, ldap.SCOPE_BASE, - '(objectclass=*)')[0] - srvlist = ret.data.get('defaultServerList') - if len(srvlist) > 0: - srvlist = srvlist[0].split() + entry = self.admin_conn.getEntry(dn, ldap.SCOPE_BASE, '(objectclass=*)') + srvlist = entry.getValue('defaultServerList', '') + srvlist = srvlist.split() if not self.fqdn in srvlist: srvlist.append(self.fqdn) attr = ' '.join(srvlist) mod = [(ldap.MOD_REPLACE, 'defaultServerList', attr)] self.admin_conn.modify_s(dn, mod) - except ldap.NO_SUCH_OBJECT: + except errors.NotFound: pass except ldap.TYPE_OR_VALUE_EXISTS: pass self.ldap_disconnect() - diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index 601f76bb7..e5d9f080b 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -57,6 +57,8 @@ class HTTPInstance(service.Service): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + subject_base = ipautil.dn_attribute_property('_subject_base') + def create_instance(self, realm, fqdn, domain_name, dm_password=None, autoconfig=True, pkcs12_info=None, self_signed_ca=False, subject_base=None, auto_redirect=True): self.fqdn = fqdn self.realm = realm diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 388a11e26..048706523 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -44,6 +44,7 @@ from ipapython.ipa_log_manager import * from ipalib.util import validate_hostname from ipapython import config from ipalib import errors +from ipapython.dn import DN # Used to determine install status IPA_MODULES = ['httpd', 'kadmin', 'dirsrv', 'pki-cad', 'pkids', 'install', 'krb5kdc', 'ntpd', 'named', 'ipa_memcached'] @@ -71,9 +72,11 @@ class ReplicaConfig: self.dirman_password = "" self.host_name = "" self.dir = "" - self.subject_base = "" + self.subject_base = None self.setup_ca = False + subject_base = ipautil.dn_attribute_property('_subject_base') + def get_fqdn(): fqdn = "" try: diff --git a/ipaserver/install/ipa_ldap_updater.py b/ipaserver/install/ipa_ldap_updater.py index 794ea28b5..d9f680927 100644 --- a/ipaserver/install/ipa_ldap_updater.py +++ b/ipaserver/install/ipa_ldap_updater.py @@ -150,6 +150,8 @@ class LDAPUpdater_Upgrade(LDAPUpdater): class LDAPUpdater_NonUpgrade(LDAPUpdater): + log_file_name = '/var/log/ipaupgrade.log' + def validate_options(self): super(LDAPUpdater_NonUpgrade, self).validate_options() options = self.options diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index 8cc50fba4..9101e6fcc 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -33,6 +33,7 @@ from ipapython import services as ipaservices from ipalib import util from ipalib import errors from ipapython.ipa_log_manager import * +from ipapython.dn import DN from ipaserver import ipaldap from ipaserver.install import replication @@ -84,6 +85,7 @@ class KrbInstance(service.Service): self.admin_password = None self.master_password = None self.suffix = None + self.subject_base = None self.kdc_password = None self.sub_dict = None self.pkcs12_info = None @@ -94,8 +96,11 @@ class KrbInstance(service.Service): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + suffix = ipautil.dn_attribute_property('_suffix') + subject_base = ipautil.dn_attribute_property('_subject_base') + def get_realm_suffix(self): - return "cn=%s,cn=kerberos,%s" % (self.realm, self.suffix) + return DN(('cn', self.realm), ('cn', 'kerberos'), self.suffix) def move_service_to_host(self, principal): """ @@ -103,12 +108,12 @@ class KrbInstance(service.Service): cn=kerberos to reside under the host entry. """ - service_dn = "krbprincipalname=%s,%s" % (principal, self.get_realm_suffix()) + service_dn = DN(('krbprincipalname', principal), self.get_realm_suffix()) service_entry = self.admin_conn.getEntry(service_dn, ldap.SCOPE_BASE) self.admin_conn.deleteEntry(service_dn) # Create a host entry for this master - host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) + host_dn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) host_entry = ipaldap.Entry(host_dn) host_entry.setValues('objectclass', ['top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice', 'pkiuser', 'krbprincipalaux', 'krbprincipal', 'krbticketpolicyaux', 'ipasshhost']) host_entry.setValues('krbextradata', service_entry.getValues('krbextradata')) @@ -251,7 +256,7 @@ class KrbInstance(service.Service): # they may conflict. try: - res = self.admin_conn.search_s("cn=mapping,cn=sasl,cn=config", + res = self.admin_conn.search_s(DN(('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config')), ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)") for r in res: @@ -264,7 +269,7 @@ class KrbInstance(service.Service): root_logger.critical("Error while enumerating SASL mappings %s" % str(e)) raise e - entry = ipaldap.Entry("cn=Full Principal,cn=mapping,cn=sasl,cn=config") + entry = ipaldap.Entry(DN(('cn', 'Full Principal'), ('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsSaslMapping") entry.setValues("cn", "Full Principal") entry.setValues("nsSaslMapRegexString", '\(.*\)@\(.*\)') @@ -277,7 +282,7 @@ class KrbInstance(service.Service): root_logger.critical("failed to add Full Principal Sasl mapping") raise e - entry = ipaldap.Entry("cn=Name Only,cn=mapping,cn=sasl,cn=config") + entry = ipaldap.Entry(DN(('cn', 'Name Only'), ('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsSaslMapping") entry.setValues("cn", "Name Only") entry.setValues("nsSaslMapRegexString", '^[^:@]+$') @@ -358,7 +363,7 @@ class KrbInstance(service.Service): root_logger.critical("Could not find master key in DS") raise e - krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey) + krbMKey = pyasn1.codec.ber.decoder.decode(entry.getValue('krbmkey')) keytype = int(krbMKey[0][1][0]) keydata = str(krbMKey[0][1][1]) @@ -431,7 +436,7 @@ class KrbInstance(service.Service): # Create the special anonymous principal installutils.kadmin_addprinc(princ_realm) - dn = "krbprincipalname=%s,%s" % (princ_realm, self.get_realm_suffix()) + dn = DN(('krbprincipalname', princ_realm), self.get_realm_suffix()) self.admin_conn.inactivateEntry(dn, False) def __convert_to_gssapi_replication(self): @@ -439,7 +444,7 @@ class KrbInstance(service.Service): self.fqdn, self.dm_password) repl.convert_to_gssapi_replication(self.master_fqdn, - r_binddn="cn=Directory Manager", + r_binddn=DN(('cn', 'Directory Manager')), r_bindpw=self.dm_password) def uninstall(self): diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py index 949b86ad9..a51edff99 100644 --- a/ipaserver/install/ldapupdate.py +++ b/ipaserver/install/ldapupdate.py @@ -33,9 +33,8 @@ import uuid from ipalib import util from ipalib import errors from ipalib import api -from ipalib.dn import DN +from ipapython.dn import DN import ldap -from ldap.dn import escape_dn_chars from ipapython.ipa_log_manager import * import krbV import platform @@ -52,22 +51,62 @@ from ipaserver.install.plugins import FIRST, MIDDLE, LAST class BadSyntax(installutils.ScriptError): def __init__(self, value): self.value = value - self.msg = "There is a syntax error in this update file: \n %s" % value + self.msg = "LDAPUpdate: syntax error: \n %s" % value self.rval = 1 def __str__(self): return repr(self.value) class LDAPUpdate: + action_keywords = ["default", "add", "remove", "only", "deleteentry", "replace", "addifnew", "addifexist"] + def __init__(self, dm_password, sub_dict={}, live_run=True, online=True, ldapi=False, plugins=False): - """dm_password = Directory Manager password - sub_dict = substitution dictionary - live_run = Apply the changes or just test - online = do an online LDAP update or use an experimental LDIF updater - ldapi = bind using ldapi. This assumes autobind is enabled. - plugins = execute the pre/post update plugins - """ + ''' + :parameters: + dm_password + Directory Manager password + sub_dict + substitution dictionary + live_run + Apply the changes or just test + online + Do an online LDAP update or use an experimental LDIF updater + ldapi + Bind using ldapi. This assumes autobind is enabled. + plugins + execute the pre/post update plugins + + Data Structure Example: + ----------------------- + + dn_by_rdn_count = { + 3: 'cn=config,dc=example,dc=com': + 4: 'cn=bob,ou=people,dc=example,dc=com', + } + + all_updates = { + 'dn': 'cn=config,dc=example,dc=com': + { + 'dn': 'cn=config,dc=example,dc=com', + 'default': ['attr1':default1'], + 'updates': ['action:attr1:value1', + 'action:attr2:value2] + }, + 'dn': 'cn=bob,ou=people,dc=example,dc=com': + { + 'dn': 'cn=bob,ou=people,dc=example,dc=com', + 'default': ['attr3':default3'], + 'updates': ['action:attr3:value3', + 'action:attr4:value4], + } + } + + The default and update lists are "dispositions" + + + ''' + log_mgr.get_logger(self, True) self.sub_dict = sub_dict self.live_run = live_run self.dm_password = dm_password @@ -77,6 +116,8 @@ class LDAPUpdate: self.ldapi = ldapi self.plugins = plugins self.pw_name = pwd.getpwuid(os.geteuid()).pw_name + self.realm = None + suffix = None if sub_dict.get("REALM"): self.realm = sub_dict["REALM"] @@ -89,8 +130,10 @@ class LDAPUpdate: self.realm = None suffix = None + if suffix is not None: + assert isinstance(suffix, DN) domain = ipautil.get_domain_name() - libarch = self.__identify_arch() + libarch = self._identify_arch() fqdn = installutils.get_fqdn() if fqdn is None: @@ -110,7 +153,7 @@ class LDAPUpdate: if not self.sub_dict.get("SUFFIX") and suffix is not None: self.sub_dict["SUFFIX"] = suffix if not self.sub_dict.get("ESCAPED_SUFFIX"): - self.sub_dict["ESCAPED_SUFFIX"] = escape_dn_chars(suffix) + self.sub_dict["ESCAPED_SUFFIX"] = str(suffix) if not self.sub_dict.get("LIBARCH"): self.sub_dict["LIBARCH"] = libarch if not self.sub_dict.get("TIME"): @@ -123,7 +166,7 @@ class LDAPUpdate: try: conn = ipaldap.IPAdmin(fqdn, ldapi=self.ldapi, realm=self.realm) if self.dm_password: - conn.do_simple_bind(binddn="cn=directory manager", bindpw=self.dm_password) + conn.do_simple_bind(binddn=DN(('cn', 'directory manager')), bindpw=self.dm_password) elif os.getegid() == 0: try: # autobind @@ -145,13 +188,13 @@ class LDAPUpdate: # The following 2 functions were taken from the Python # documentation at http://docs.python.org/library/csv.html - def __utf_8_encoder(self, unicode_csv_data): + def _utf_8_encoder(self, unicode_csv_data): for line in unicode_csv_data: yield line.encode('utf-8') - def __unicode_csv_reader(self, unicode_csv_data, quote_char="'", dialect=csv.excel, **kwargs): + def _unicode_csv_reader(self, unicode_csv_data, quote_char="'", dialect=csv.excel, **kwargs): # csv.py doesn't do Unicode; encode temporarily as UTF-8: - csv_reader = csv.reader(self.__utf_8_encoder(unicode_csv_data), + csv_reader = csv.reader(self._utf_8_encoder(unicode_csv_data), dialect=dialect, delimiter=',', quotechar=quote_char, skipinitialspace=True, @@ -160,7 +203,7 @@ class LDAPUpdate: # decode UTF-8 back to Unicode, cell by cell: yield [unicode(cell, 'utf-8') for cell in row] - def __identify_arch(self): + def _identify_arch(self): """On multi-arch systems some libraries may be in /lib64, /usr/lib64, etc. Determine if a suffix is needed based on the current architecture. @@ -178,7 +221,7 @@ class LDAPUpdate: except KeyError, e: raise BadSyntax("Unknown template keyword %s" % e) - def __parse_values(self, line): + def _parse_values(self, line): """Parse a comma-separated string into separate values and convert them into a list. This should handle quoted-strings with embedded commas """ @@ -186,7 +229,7 @@ class LDAPUpdate: quote_char = "'" else: quote_char = '"' - reader = self.__unicode_csv_reader([line], quote_char) + reader = self._unicode_csv_reader([line], quote_char) value = [] for row in reader: value = value + row @@ -201,7 +244,7 @@ class LDAPUpdate: if fd != sys.stdin: fd.close() return text - def __entry_to_entity(self, ent): + def _entry_to_entity(self, ent): """Tne Entry class is a bare LDAP entry. The Entity class has a lot more helper functions that we need, so convert to dict and then to Entity. """ @@ -215,113 +258,158 @@ class LDAPUpdate: entry[key] = value[0] return entity.Entity(entry) - def __combine_updates(self, dn_list, all_updates, update): - """Combine a new update with the list of total updates - - Updates are stored in 2 lists: - dn_list: contains a unique list of DNs in the updates - all_updates: the actual updates that need to be applied - - We want to apply the updates from the shortest to the longest - path so if new child and parent entries are in different updates - we can be sure the parent gets written first. This also lets - us apply any schema first since it is in the very short cn=schema. - """ + def _combine_updates(self, all_updates, update): + 'Combine a new update with the list of total updates' dn = update.get('dn') - dns = ldap.explode_dn(dn.lower()) - l = len(dns) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] + assert isinstance(dn, DN) + if not all_updates.get(dn): all_updates[dn] = update return all_updates - e = all_updates[dn] + existing_update = all_updates[dn] if 'default' in update: - if 'default' in e: - e['default'] = e['default'] + update['default'] - else: - e['default'] = update['default'] + disposition_list = existing_update.setdefault('default', []) + disposition_list.extend(update['default']) elif 'updates' in update: - if 'updates' in e: - e['updates'] = e['updates'] + update['updates'] - else: - e['updates'] = update['updates'] + disposition_list = existing_update.setdefault('updates', []) + disposition_list.extend(update['updates']) else: - root_logger.debug("Unknown key in updates %s" % update.keys()) - - all_updates[dn] = e + self.debug("Unknown key in updates %s" % update.keys()) + + def merge_updates(self, all_updates, updates): + ''' + Add the new_update dict to the all_updates dict. If an entry + in the new_update already has an entry in all_updates merge + the two entries sensibly assuming the new entries take + precedence. Otherwise just add the new entry. + ''' + + for new_update in updates: + for new_dn, new_entry in new_update.iteritems(): + existing_entry = all_updates.get(new_dn) + if existing_entry: + # If the existing entry is marked for deletion but the + # new entry is not also a delete then clear the delete + # flag otherwise the newer update will be lost. + if existing_entry.has_key('deleteentry') and not new_entry.has_key('deleteentry'): + self.warning("ldapupdate: entry '%s' previously marked for deletion but" + + " this subsequent update reestablishes it: %s", new_dn, new_entry) + del existing_entry['deleteentry'] + existing_entry.update(new_entry) + else: + all_updates[new_dn] = new_entry - return all_updates - def parse_update_file(self, data, all_updates, dn_list): + def parse_update_file(self, data_source_name, source_data, all_updates): """Parse the update file into a dictonary of lists and apply the update for each DN in the file.""" - valid_keywords = ["default", "add", "remove", "only", "deleteentry", "replace", "addifnew", "addifexist"] update = {} - d = "" - index = "" + logical_line = "" + action = "" dn = None lcount = 0 - for line in data: - # Strip out \n and extra white space - lcount = lcount + 1 - # skip comments and empty lines - line = line.rstrip() - if line.startswith('#') or line == '': continue + def emit_item(logical_line): + ''' + Given a logical line containing an item to process perform the following: - if line.lower().startswith('dn:'): - if dn is not None: - all_updates = self.__combine_updates(dn_list, all_updates, update) + * Strip leading & trailing whitespace + * Substitute any variables + * Get the action, attribute, and value + * Each update has one list per disposition, append to specified disposition list + ''' - update = {} - dn = line[3:].strip() - update['dn'] = self._template_str(dn) - else: - if dn is None: - raise BadSyntax, "dn is not defined in the update" - - line = self._template_str(line) - if line.startswith(' '): - v = d[len(d) - 1] - v = v + line[1:] - d[len(d) - 1] = v - update[index] = d - continue - line = line.strip() - values = line.split(':', 2) - if len(values) != 3: - raise BadSyntax, "Bad formatting on line %d: %s" % (lcount,line) + logical_line = logical_line.strip() + if logical_line == '': + return + + # Perform variable substitution on constructued line + logical_line = self._template_str(logical_line) - index = values[0].strip().lower() + items = logical_line.split(':', 2) - if index not in valid_keywords: - raise BadSyntax, "Unknown keyword %s" % index + if len(items) == 0: + raise BadSyntax, "Bad formatting on line %s:%d: %s" % (data_source_name, lcount, logical_line) - attr = values[1].strip() - value = values[2].strip() + action = items[0].strip().lower() - new_value = "" - if index == "default": + if action not in self.action_keywords: + raise BadSyntax, "Unknown update action '%s', data source=%s" % (action, data_source_name) + + if action == 'deleteentry': + new_value = None + disposition = "deleteentry" + else: + if len(items) != 3: + raise BadSyntax, "Bad formatting on line %s:%d: %s" % (data_source_name, lcount, logical_line) + + attr = items[1].strip() + value = items[2].strip() + + if action == "default": new_value = attr + ":" + value + disposition = "default" else: - new_value = index + ":" + attr + ":" + value - index = "updates" + new_value = action + ":" + attr + ":" + value + disposition = "updates" + + disposition_list = update.setdefault(disposition, []) + disposition_list.append(new_value) + + def emit_update(update): + ''' + When processing a dn is completed emit the update by merging it into + the set of all updates. + ''' + + self._combine_updates(all_updates, update) + + # Iterate over source input lines + for source_line in source_data: + lcount += 1 + + # strip trailing whitespace and newline + source_line = source_line.rstrip() - d = update.get(index, []) + # skip comments and empty lines + if source_line.startswith('#') or source_line == '': + continue - d.append(new_value) + if source_line.lower().startswith('dn:'): + # Starting new dn + if dn is not None: + # Emit previous dn + emit_item(logical_line) + logical_line = '' + emit_update(update) + update = {} + + dn = source_line[3:].strip() + dn = DN(self._template_str(dn)) + update['dn'] = dn + else: + # Process items belonging to dn + if dn is None: + raise BadSyntax, "dn is not defined in the update, data source=%s" % (data_source_name) - update[index] = d + # If continuation line, append to existing logical line & continue, + # otherwise flush the previous item. + if source_line.startswith(' '): + logical_line += source_line[1:] + continue + else: + emit_item(logical_line) + logical_line = '' + logical_line = source_line if dn is not None: - all_updates = self.__combine_updates(dn_list, all_updates, update) + emit_item(logical_line) + logical_line = '' + emit_update(update) + update = {} - return (all_updates, dn_list) + return all_updates def create_index_task(self, attribute): """Create a task to update an index for an attribute""" @@ -337,15 +425,15 @@ class LDAPUpdate: cn = "indextask_%s_%s_%s" % (attribute, cn_uuid.time, cn_uuid.clock_seq) dn = DN(('cn', cn), ('cn', 'index'), ('cn', 'tasks'), ('cn', 'config')) - e = ipaldap.Entry(str(dn)) + e = ipaldap.Entry(dn) e.setValues('objectClass', ['top', 'extensibleObject']) e.setValue('cn', cn) e.setValue('nsInstance', 'userRoot') e.setValues('nsIndexAttribute', attribute) - root_logger.info("Creating task to index attribute: %s", attribute) - root_logger.debug("Task id: %s", dn) + self.info("Creating task to index attribute: %s", attribute) + self.debug("Task id: %s", dn) if self.live_run: self.conn.addEntry(e) @@ -356,6 +444,8 @@ class LDAPUpdate: """Give a task DN monitor it and wait until it has completed (or failed) """ + assert isinstance(dn, DN) + if not self.live_run: # If not doing this live there is nothing to monitor return @@ -370,10 +460,10 @@ class LDAPUpdate: try: entry = self.conn.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) except errors.NotFound, e: - root_logger.error("Task not found: %s", dn) + self.error("Task not found: %s", dn) return except errors.DatabaseError, e: - root_logger.error("Task lookup failure %s", e) + self.error("Task lookup failure %s", e) return status = entry.getValue('nstaskstatus') @@ -383,200 +473,158 @@ class LDAPUpdate: continue if status.lower().find("finished") > -1: - root_logger.info("Indexing finished") + self.info("Indexing finished") break - root_logger.debug("Indexing in progress") + self.debug("Indexing in progress") time.sleep(1) return - def __create_default_entry(self, dn, default): + def _create_default_entry(self, dn, default): """Create the default entry from the values provided. The return type is entity.Entity """ + assert isinstance(dn, DN) entry = ipaldap.Entry(dn) if not default: # This means that the entire entry needs to be created with add - return self.__entry_to_entity(entry) + return self._entry_to_entity(entry) - for line in default: + for item in default: # We already do syntax-parsing so this is safe - (k, v) = line.split(':',1) - e = entry.getValues(k) + (attr, value) = item.split(':',1) + e = entry.getValues(attr) if e: # multi-valued attribute e = list(e) - e.append(v) + e.append(value) else: - e = v - entry.setValues(k, e) + e = value + entry.setValues(attr, e) - return self.__entry_to_entity(entry) + return self._entry_to_entity(entry) - def __get_entry(self, dn): + def _get_entry(self, dn): """Retrieve an object from LDAP. The return type is ipaldap.Entry """ + assert isinstance(dn, DN) searchfilter="objectclass=*" sattrs = ["*", "aci", "attributeTypes", "objectClasses"] scope = ldap.SCOPE_BASE return self.conn.getList(dn, scope, searchfilter, sattrs) - def __update_managed_entries(self): - """Update and move legacy Managed Entry Plugins.""" - - suffix = ipautil.realm_to_suffix(self.realm) - searchfilter = '(objectclass=*)' - definitions_managed_entries = [] - old_template_container = 'cn=etc,%s' % suffix - old_definition_container = 'cn=Managed Entries,cn=plugins,cn=config' - new = 'cn=Managed Entries,cn=etc,%s' % suffix - sub = ['cn=Definitions,', 'cn=Templates,'] - new_managed_entries = [] - old_templates = [] - template = None - try: - definitions_managed_entries = self.conn.getList(old_definition_container, ldap.SCOPE_ONELEVEL, searchfilter,[]) - except errors.NotFound, e: - return new_managed_entries - for entry in definitions_managed_entries: - new_definition = {} - definition_managed_entry_updates = {} - definitions_managed_entries - old_definition = {'dn': entry.dn, 'deleteentry': ['dn: %s' % entry.dn]} - old_template = entry.getValue('managedtemplate') - entry.setValues('managedtemplate', entry.getValue('managedtemplate').replace(old_template_container, sub[1] + new)) - new_definition['dn'] = entry.dn.replace(old_definition_container, sub[0] + new) - new_definition['default'] = str(entry).strip().replace(': ', ':').split('\n')[1:] - definition_managed_entry_updates[new_definition['dn']] = new_definition - definition_managed_entry_updates[old_definition['dn']] = old_definition - old_templates.append(old_template) - new_managed_entries.append(definition_managed_entry_updates) - for old_template in old_templates: - try: - template = self.conn.getEntry(old_template, ldap.SCOPE_BASE, searchfilter,[]) - new_template = {} - template_managed_entry_updates = {} - old_template = {'dn': template.dn, 'deleteentry': ['dn: %s' % template.dn]} - new_template['dn'] = template.dn.replace(old_template_container, sub[1] + new) - new_template['default'] = str(template).strip().replace(': ', ':').split('\n')[1:] - template_managed_entry_updates[new_template['dn']] = new_template - template_managed_entry_updates[old_template['dn']] = old_template - new_managed_entries.append(template_managed_entry_updates) - except errors.NotFound, e: - pass - if len(new_managed_entries) > 0: - new_managed_entries.sort(reverse=True) - - return new_managed_entries - - def __apply_updates(self, updates, entry): - """updates is a list of changes to apply - entry is the thing to apply them to + def _apply_update_disposition(self, updates, entry): + """ + updates is a list of changes to apply + entry is the thing to apply them to - Returns the modified entry + Returns the modified entry """ if not updates: return entry only = {} - for u in updates: + for update in updates: # We already do syntax-parsing so this is safe - (utype, k, values) = u.split(':',2) - values = self.__parse_values(values) - - e = entry.getValues(k) - if not isinstance(e, list): - if e is None: - e = [] + (action, attr, update_values) = update.split(':',2) + update_values = self._parse_values(update_values) + + # If the attribute is known to be a DN convert it to a DN object. + # This has to be done after _parse_values() due to quoting and comma separated lists. + if self.conn.has_dn_syntax(attr): + update_values = [DN(x) for x in update_values] + + entry_values = entry.getValues(attr) + if not isinstance(entry_values, list): + if entry_values is None: + entry_values = [] else: - e = [e] - for v in values: - if utype == 'remove': - root_logger.debug("remove: '%s' from %s, current value %s", v, k, e) + entry_values = [entry_values] + for update_value in update_values: + if action == 'remove': + self.debug("remove: '%s' from %s, current value %s", update_value, attr, entry_values) try: - e.remove(v) + entry_values.remove(update_value) except ValueError: - root_logger.warning("remove: '%s' not in %s", v, k) + self.warning("remove: '%s' not in %s", update_value, attr) pass - entry.setValues(k, e) - root_logger.debug('remove: updated value %s', e) - elif utype == 'add': - root_logger.debug("add: '%s' to %s, current value %s", v, k, e) + entry.setValues(attr, entry_values) + self.debug('remove: updated value %s', entry_values) + elif action == 'add': + self.debug("add: '%s' to %s, current value %s", update_value, attr, entry_values) # Remove it, ignoring errors so we can blindly add it later try: - e.remove(v) + entry_values.remove(update_value) except ValueError: pass - e.append(v) - root_logger.debug('add: updated value %s', e) - entry.setValues(k, e) - elif utype == 'addifnew': - root_logger.debug("addifnew: '%s' to %s, current value %s", v, k, e) + entry_values.append(update_value) + self.debug('add: updated value %s', entry_values) + entry.setValues(attr, entry_values) + elif action == 'addifnew': + self.debug("addifnew: '%s' to %s, current value %s", update_value, attr, entry_values) # Only add the attribute if it doesn't exist. Only works # with single-value attributes. - if len(e) == 0: - e.append(v) - root_logger.debug('addifnew: set %s to %s', k, e) - entry.setValues(k, e) - elif utype == 'addifexist': - root_logger.debug("addifexist: '%s' to %s, current value %s", v, k, e) + if len(entry_values) == 0: + entry_values.append(update_value) + self.debug('addifnew: set %s to %s', attr, entry_values) + entry.setValues(attr, entry_values) + elif action == 'addifexist': + self.debug("addifexist: '%s' to %s, current value %s", update_value, attr, entry_values) # Only add the attribute if the entry doesn't exist. We # determine this based on whether it has an objectclass if entry.getValues('objectclass'): - e.append(v) - root_logger.debug('addifexist: set %s to %s', k, e) - entry.setValues(k, e) - elif utype == 'only': - root_logger.debug("only: set %s to '%s', current value %s", k, v, e) - if only.get(k): - e.append(v) + entry_values.append(update_value) + self.debug('addifexist: set %s to %s', attr, entry_values) + entry.setValues(attr, entry_values) + elif action == 'only': + self.debug("only: set %s to '%s', current value %s", attr, update_value, entry_values) + if only.get(attr): + entry_values.append(update_value) else: - e = [v] - only[k] = True - entry.setValues(k, e) - root_logger.debug('only: updated value %s', e) - elif utype == 'deleteentry': + entry_values = [update_value] + only[attr] = True + entry.setValues(attr, entry_values) + self.debug('only: updated value %s', entry_values) + elif action == 'deleteentry': # skip this update type, it occurs in __delete_entries() return None - elif utype == 'replace': - # v has the format "old::new" + elif action == 'replace': + # value has the format "old::new" try: - (old, new) = v.split('::', 1) + (old, new) = update_value.split('::', 1) except ValueError: - raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % v + raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % update_value try: - e.remove(old) - e.append(new) - root_logger.debug('replace: updated value %s', e) - entry.setValues(k, e) + entry_values.remove(old) + entry_values.append(new) + self.debug('replace: updated value %s', entry_values) + entry.setValues(attr, entry_values) except ValueError: - root_logger.debug('replace: %s not found, skipping', old) - - self.print_entity(entry) + self.debug('replace: %s not found, skipping', old) return entry def print_entity(self, e, message=None): """The entity object currently lacks a str() method""" - root_logger.debug("---------------------------------------------") + self.debug("---------------------------------------------") if message: - root_logger.debug("%s", message) - root_logger.debug("dn: " + e.dn) + self.debug("%s", message) + self.debug("dn: %s", e.dn) attr = e.attrList() for a in attr: value = e.getValues(a) - if isinstance(value,str): - root_logger.debug(a + ": " + value) - else: - root_logger.debug(a + ": ") + if isinstance(value, (list, tuple)): + self.debug('%s:', a) for l in value: - root_logger.debug("\t" + l) + self.debug("\t%s", l) + else: + self.debug('%s: %s', a, value) def is_schema_updated(self, s): """Compare the schema in 's' with the current schema in the DS to @@ -598,48 +646,59 @@ class LDAPUpdate: s = s.ldap_entry() # Get a fresh copy and convert into a SubSchema - n = self.__get_entry("cn=schema")[0] - n = dict(n.data) - n = ldap.schema.SubSchema(n) + n = self._get_entry(DN(('cn', 'schema')))[0] + + # Convert IPA data types back to strings + d = dict() + for k,v in n.data.items(): + d[k] = [str(x) for x in v] + + # Convert to subschema dict + n = ldap.schema.SubSchema(d) n = n.ldap_entry() + # Are they equal? if s == n: return False else: return True - def __update_record(self, update): + def _update_record(self, update): found = False - new_entry = self.__create_default_entry(update.get('dn'), - update.get('default')) + # If the entry is going to be deleted no point in processing it. + if update.has_key('deleteentry'): + return + + new_entry = self._create_default_entry(update.get('dn'), + update.get('default')) try: - e = self.__get_entry(new_entry.dn) + e = self._get_entry(new_entry.dn) if len(e) > 1: # we should only ever get back one entry raise BadSyntax, "More than 1 entry returned on a dn search!? %s" % new_entry.dn - entry = self.__entry_to_entity(e[0]) + entry = self._entry_to_entity(e[0]) found = True - root_logger.info("Updating existing entry: %s", entry.dn) + self.info("Updating existing entry: %s", entry.dn) except errors.NotFound: # Doesn't exist, start with the default entry entry = new_entry - root_logger.info("New entry: %s", entry.dn) + self.info("New entry: %s", entry.dn) except errors.DatabaseError: # Doesn't exist, start with the default entry entry = new_entry - root_logger.info("New entry, using default value: %s", entry.dn) + self.info("New entry, using default value: %s", entry.dn) - self.print_entity(entry) + self.print_entity(entry, "Initial value") # Bring this entry up to date - entry = self.__apply_updates(update.get('updates'), entry) + entry = self._apply_update_disposition(update.get('updates'), entry) if entry is None: # It might be None if it is just deleting an entry return - self.print_entity(entry, "Final value") + self.print_entity(entry, "Final value after applying updates") if not found: # New entries get their orig_data set to the entry itself. We want to @@ -657,47 +716,47 @@ class LDAPUpdate: except errors.NotFound: # parent entry of the added entry does not exist # this may not be an error (e.g. entries in NIS container) - root_logger.info("Parent DN of %s may not exist, cannot create the entry", + self.info("Parent DN of %s may not exist, cannot create the entry", entry.dn) return self.modified = True except Exception, e: - root_logger.error("Add failure %s", e) + self.error("Add failure %s", e) else: # Update LDAP try: updated = False changes = self.conn.generateModList(entry.origDataDict(), entry.toDict()) - if (entry.dn == "cn=schema"): + if (entry.dn == DN(('cn', 'schema'))): updated = self.is_schema_updated(entry.toDict()) else: if len(changes) >= 1: updated = True - root_logger.debug("%s" % changes) - root_logger.debug("Live %d, updated %d" % (self.live_run, updated)) + self.debug("%s" % changes) + self.debug("Live %d, updated %d" % (self.live_run, updated)) if self.live_run and updated: self.conn.updateEntry(entry.dn, entry.origDataDict(), entry.toDict()) - root_logger.info("Done") + self.info("Done") except errors.EmptyModlist: - root_logger.info("Entry already up-to-date") + self.info("Entry already up-to-date") updated = False except errors.DatabaseError, e: - root_logger.error("Update failed: %s", e) + self.error("Update failed: %s", e) updated = False except errors.ACIError, e: - root_logger.error("Update failed: %s", e) + self.error("Update failed: %s", e) updated = False - if ("cn=index" in entry.dn and - "cn=userRoot" in entry.dn): - taskid = self.create_index_task(entry.cn) + if (DN(('cn', 'index')) in entry.dn and + DN(('cn', 'userRoot')) in entry.dn): + taskid = self.create_index_task(entry.getValue('cn')) self.monitor_index_task(taskid) if updated: self.modified = True return - def __delete_record(self, updates): + def _delete_record(self, updates): """ Run through all the updates again looking for any that should be deleted. @@ -706,38 +765,21 @@ class LDAPUpdate: considered first so we don't end up trying to delete a parent and child in the wrong order. """ - dn = updates['dn'] - deletes = updates.get('deleteentry', []) - for d in deletes: - try: - root_logger.info("Deleting entry %s", dn) - if self.live_run: - self.conn.deleteEntry(dn) - self.modified = True - except errors.NotFound, e: - root_logger.info("%s did not exist:%s", dn, e) - self.modified = True - except errors.DatabaseError, e: - root_logger.error("Delete failed: %s", e) - - updates = updates.get('updates', []) - for u in updates: - # We already do syntax-parsing so this is safe - (utype, k, values) = u.split(':',2) - if utype == 'deleteentry': - try: - root_logger.info("Deleting entry %s", dn) - if self.live_run: - self.conn.deleteEntry(dn) - self.modified = True - except errors.NotFound, e: - root_logger.info("%s did not exist:%s", dn, e) - self.modified = True - except errors.DatabaseError, e: - root_logger.error("Delete failed: %s", e) + if not updates.has_key('deleteentry'): + return - return + dn = updates['dn'] + try: + self.info("Deleting entry %s", dn) + if self.live_run: + self.conn.deleteEntry(dn) + self.modified = True + except errors.NotFound, e: + self.info("%s did not exist:%s", dn, e) + self.modified = True + except errors.DatabaseError, e: + self.error("Delete failed: %s", e) def get_all_files(self, root, recursive=False): """Get all update files""" @@ -761,7 +803,7 @@ class LDAPUpdate: realm=self.realm) try: if self.dm_password: - self.conn.do_simple_bind(binddn="cn=directory manager", bindpw=self.dm_password) + self.conn.do_simple_bind(binddn=DN(('cn', 'directory manager')), bindpw=self.dm_password) elif os.getegid() == 0: try: # autobind @@ -776,19 +818,28 @@ class LDAPUpdate: else: raise RuntimeError("Offline updates are not supported.") - def __run_updates(self, dn_list, all_updates): + def _run_updates(self, all_updates): # For adds and updates we want to apply updates from shortest # to greatest length of the DN. For deletes we want the reverse. - sortedkeys = dn_list.keys() + + dn_by_rdn_count = {} + for dn in all_updates.keys(): + assert isinstance(dn, DN) + rdn_count = len(dn) + rdn_count_list = dn_by_rdn_count.setdefault(rdn_count, []) + if dn not in rdn_count_list: + rdn_count_list.append(dn) + + sortedkeys = dn_by_rdn_count.keys() sortedkeys.sort() - for k in sortedkeys: - for dn in dn_list[k]: - self.__update_record(all_updates[dn]) + for rdn_count in sortedkeys: + for dn in dn_by_rdn_count[rdn_count]: + self._update_record(all_updates[dn]) sortedkeys.reverse() - for k in sortedkeys: - for dn in dn_list[k]: - self.__delete_record(all_updates[dn]) + for rdn_count in sortedkeys: + for dn in dn_by_rdn_count[rdn_count]: + self._delete_record(all_updates[dn]) def update(self, files): """Execute the update. files is a list of the update files to use. @@ -796,71 +847,46 @@ class LDAPUpdate: returns True if anything was changed, otherwise False """ - updates = None + all_updates = {} if self.plugins: - root_logger.info('PRE_UPDATE') + self.info('PRE_UPDATE') updates = api.Backend.updateclient.update(PRE_UPDATE, self.dm_password, self.ldapi, self.live_run) - + self.merge_updates(all_updates, updates) try: self.create_connection() - all_updates = {} - dn_list = {} - # Start with any updates passed in from pre-update plugins - if updates: - for entry in updates: - all_updates.update(entry) - for upd in updates: - for dn in upd: - dn_explode = ldap.explode_dn(dn.lower()) - l = len(dn_explode) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] for f in files: try: - root_logger.info("Parsing file %s" % f) + self.info("Parsing update file '%s'" % f) data = self.read_file(f) except Exception, e: - print e + self.error("error reading update file '%s'", f) sys.exit(e) - (all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list) + self.parse_update_file(f, data, all_updates) - self.__run_updates(dn_list, all_updates) + self._run_updates(all_updates) finally: if self.conn: self.conn.unbind() if self.plugins: - root_logger.info('POST_UPDATE') + self.info('POST_UPDATE') + all_updates = {} updates = api.Backend.updateclient.update(POST_UPDATE, self.dm_password, self.ldapi, self.live_run) - dn_list = {} - for upd in updates: - for dn in upd: - dn_explode = ldap.explode_dn(dn.lower()) - l = len(dn_explode) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] - self.__run_updates(dn_list, updates) + self.merge_updates(all_updates, updates) + self._run_updates(all_updates) return self.modified - def update_from_dict(self, dn_list, updates): + def update_from_dict(self, updates): """ Apply updates internally as opposed to from a file. - - dn_list is a list of dns to be updated updates is a dictionary containing the updates """ if not self.conn: self.create_connection() - self.__run_updates(dn_list, updates) + self._run_updates(updates) return self.modified diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py index c32d82e3a..555a28b8f 100644 --- a/ipaserver/install/plugins/adtrust.py +++ b/ipaserver/install/plugins/adtrust.py @@ -20,7 +20,7 @@ from ipaserver.install.plugins import MIDDLE from ipaserver.install.plugins.baseupdate import PostUpdate from ipalib import api, errors -from ipalib.dn import DN +from ipapython.dn import DN from ipapython.ipa_log_manager import * DEFAULT_ID_RANGE_SIZE = 200000 @@ -34,7 +34,7 @@ class update_default_range(PostUpdate): def execute(self, **options): ldap = self.obj.backend - dn = str(DN(api.env.container_ranges, api.env.basedn)) + dn = DN(api.env.container_ranges, api.env.basedn) search_filter = "objectclass=ipaDomainIDRange" try: (entries, truncated) = ldap.find_entries(search_filter, [], dn) @@ -44,7 +44,7 @@ class update_default_range(PostUpdate): root_logger.debug("default_range: ipaDomainIDRange entry found, skip plugin") return (False, False, []) - dn = str(DN(('cn', 'admins'), api.env.container_group, api.env.basedn)) + dn = DN(('cn', 'admins'), api.env.container_group, api.env.basedn) try: (dn, admins_entry) = ldap.get_entry(dn, ['gidnumber']) except errors.NotFound: @@ -65,12 +65,10 @@ class update_default_range(PostUpdate): ] updates = {} - dn = str(DN(('cn', '%s_id_range' % api.env.realm), - api.env.container_ranges, api.env.basedn)) + dn = DN(('cn', '%s_id_range' % api.env.realm), + api.env.container_ranges, api.env.basedn) - # make sure everything is str or otherwise python-ldap would complain - range_entry = map(str, range_entry) - updates[dn] = {'dn' : dn, 'default' : range_entry} + updates[dn] = {'dn': dn, 'default': range_entry} # Default range entry has a hard-coded range size to 200000 which is # a default range size in ipa-server-install. This could cause issues @@ -78,7 +76,7 @@ class update_default_range(PostUpdate): # bigger range (option --idmax). # We should make our best to check if this is the case and provide # user with an information how to fix it. - dn = str(DN(api.env.container_dna_posix_ids, api.env.basedn)) + dn = DN(api.env.container_dna_posix_ids, api.env.basedn) search_filter = "objectclass=dnaSharedConfig" attrs = ['dnaHostname', 'dnaRemainingValues'] try: diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py index e11c331a4..d55596704 100644 --- a/ipaserver/install/plugins/dns.py +++ b/ipaserver/install/plugins/dns.py @@ -21,7 +21,7 @@ from ipaserver.install.plugins import MIDDLE from ipaserver.install.plugins.baseupdate import PostUpdate from ipaserver.install.plugins import baseupdate from ipalib import api, errors, util -from ipalib.dn import DN +from ipapython.dn import DN from ipalib.plugins.dns import dns_container_exists from ipapython.ipa_log_manager import * @@ -89,31 +89,29 @@ class update_dns_permissions(PostUpdate): entries otherwise. """ - _write_dns_perm_dn = DN('cn=Write DNS Configuration', - api.env.container_permission, - api.env.basedn) + _write_dns_perm_dn = DN(('cn', 'Write DNS Configuration'), + api.env.container_permission, api.env.basedn) _write_dns_perm_entry = ['objectClass:groupofnames', 'objectClass:top', 'cn:Write DNS Configuration', 'description:Write DNS Configuration', - 'member:cn=DNS Administrators,cn=privileges,cn=pbac,%s' \ - % api.env.basedn, - 'member:cn=DNS Servers,cn=privileges,cn=pbac,%s' \ - % api.env.basedn] - - _read_dns_perm_dn = DN('cn=Read DNS Entries', - api.env.container_permission, - api.env.basedn) + 'member:%s' % DN(('cn', 'DNS Administrators'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn), + 'member:%s' % DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn)] + + _read_dns_perm_dn = DN(('cn', 'Read DNS Entries'), + api.env.container_permission, api.env.basedn) _read_dns_perm_entry = ['objectClass:top', 'objectClass:groupofnames', 'objectClass:ipapermission', 'cn:Read DNS Entries', 'description:Read DNS entries', 'ipapermissiontype:SYSTEM', - 'member:cn=DNS Administrators,cn=privileges,cn=pbac,%s' \ - % api.env.basedn, - 'member:cn=DNS Servers,cn=privileges,cn=pbac,%s' \ - % api.env.basedn,] + 'member:%s' % DN(('cn', 'DNS Administrators'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn), + 'member:%s' % DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn),] _write_dns_aci_dn = DN(api.env.basedn) _write_dns_aci_entry = ['add:aci:\'(targetattr = "idnsforwardpolicy || idnsforwarders || idnsallowsyncptr || idnszonerefresh || idnspersistentsearch")(target = "ldap:///cn=dns,%(realm)s")(version 3.0;acl "permission:Write DNS Configuration";allow (write) groupdn = "ldap:///cn=Write DNS Configuration,cn=permissions,cn=pbac,%(realm)s";)\'' % dict(realm=api.env.basedn)] @@ -135,10 +133,7 @@ class update_dns_permissions(PostUpdate): (self._write_dns_aci_dn, 'updates', self._write_dns_aci_entry), (self._read_dns_aci_dn, 'updates', self._read_dns_aci_entry)): - dn = str(dn) - # make sure everything is str or otherwise python-ldap would complain - entry = map(str, entry) - dnsupdates[dn] = {'dn' : dn, container : entry} + dnsupdates[dn] = {'dn': dn, container: entry} return (False, True, [dnsupdates]) @@ -161,9 +156,9 @@ class update_dns_limits(PostUpdate): return (False, False, []) dns_principal = 'DNS/%s@%s' % (self.env.host, self.env.realm) - dns_service_dn = str(DN(('krbprincipalname', dns_principal), - self.env.container_service, - self.env.basedn)) + dns_service_dn = DN(('krbprincipalname', dns_principal), + self.env.container_service, + self.env.basedn) try: (dn, entry) = ldap.get_entry(dns_service_dn, self.limit_attributes) diff --git a/ipaserver/install/plugins/fix_replica_memberof.py b/ipaserver/install/plugins/fix_replica_memberof.py index 23bde0c9f..d4ab75348 100644 --- a/ipaserver/install/plugins/fix_replica_memberof.py +++ b/ipaserver/install/plugins/fix_replica_memberof.py @@ -44,7 +44,7 @@ class update_replica_exclude_attribute_list(PreUpdate): entries = repl.find_replication_agreements() self.log.debug("Found %d agreement(s)", len(entries)) for replica in entries: - self.log.debug(replica.description) + self.log.debug(replica.getValue('description')) attrlist = replica.getValue('nsDS5ReplicatedAttributeList') if attrlist is None: self.log.debug("Adding nsDS5ReplicatedAttributeList and nsDS5ReplicatedAttributeListTotal") @@ -68,7 +68,7 @@ class update_replica_exclude_attribute_list(PreUpdate): self.log.debug("Attribute list needs updating") current = replica.toDict() replica.setValue('nsDS5ReplicatedAttributeList', - replica.nsDS5ReplicatedAttributeList + ' %s' % ' '.join(missing)) + replica.getValue('nsDS5ReplicatedAttributeList') + ' %s' % ' '.join(missing)) try: repl.conn.updateEntry(replica.dn, current, replica.toDict()) self.log.debug("Updated") diff --git a/ipaserver/install/plugins/rename_managed.py b/ipaserver/install/plugins/rename_managed.py index a9eed0be3..99dac8148 100644 --- a/ipaserver/install/plugins/rename_managed.py +++ b/ipaserver/install/plugins/rename_managed.py @@ -24,6 +24,7 @@ from ipalib.frontend import Updater from ipaserver.install.plugins import baseupdate from ipalib import api, errors from ipapython import ipautil +from ipapython.dn import DN, EditableDN import ldap as _ldap def entry_to_update(entry): @@ -44,67 +45,99 @@ def entry_to_update(entry): return update -def generate_update(ldap, deletes=False): - """ - We need to separate the deletes that need to happen from the - new entries that need to be added. - """ - suffix = ipautil.realm_to_suffix(api.env.realm) - searchfilter = '(objectclass=*)' - definitions_managed_entries = [] - old_template_container = 'cn=etc,%s' % suffix - old_definition_container = 'cn=managed entries,cn=plugins,cn=config' - new = 'cn=Managed Entries,cn=etc,%s' % suffix - sub = ['cn=Definitions,', 'cn=Templates,'] - new_managed_entries = [] - old_templates = [] - template = None - restart = False - - # If the old entries don't exist the server has already been updated. - try: - (definitions_managed_entries, truncated) = ldap.find_entries( - searchfilter, ['*'], old_definition_container, _ldap.SCOPE_ONELEVEL, normalize=False - ) - except errors.NotFound, e: - return (False, new_managed_entries) - - for entry in definitions_managed_entries: - new_definition = {} - definition_managed_entry_updates = {} - if deletes: - old_definition = {'dn': str(entry[0]), 'deleteentry': ['dn: %s' % str(entry[0])]} - old_template = str(entry[1]['managedtemplate'][0]) - definition_managed_entry_updates[old_definition['dn']] = old_definition - old_templates.append(old_template) - else: - entry[1]['managedtemplate'] = str(entry[1]['managedtemplate'][0].replace(old_template_container, sub[1] + new)) - new_definition['dn'] = str(entry[0].replace(old_definition_container, sub[0] + new)) - new_definition['default'] = entry_to_update(entry[1]) - definition_managed_entry_updates[new_definition['dn']] = new_definition - new_managed_entries.append(definition_managed_entry_updates) - for old_template in old_templates: # Only happens when deletes is True - try: - (dn, template) = ldap.get_entry(old_template, ['*'], normalize=False) - dn = str(dn) - new_template = {} - template_managed_entry_updates = {} - old_template = {'dn': dn, 'deleteentry': ['dn: %s' % dn]} - new_template['dn'] = str(dn.replace(old_template_container, sub[1] + new)) - new_template['default'] = entry_to_update(template) - template_managed_entry_updates[new_template['dn']] = new_template - template_managed_entry_updates[old_template['dn']] = old_template - new_managed_entries.append(template_managed_entry_updates) - except errors.NotFound, e: - pass +class GenerateUpdateMixin(object): + def generate_update(self, deletes=False): + """ + We need to separate the deletes that need to happen from the + new entries that need to be added. + """ + ldap = self.obj.backend - if len(new_managed_entries) > 0: - restart = True - new_managed_entries.sort(reverse=True) + suffix = ipautil.realm_to_suffix(api.env.realm) + searchfilter = '(objectclass=*)' + definitions_managed_entries = [] - return (restart, new_managed_entries) + old_template_container = DN(('cn', 'etc'), suffix) + new_template_container = DN(('cn', 'Templates'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix) -class update_managed_post_first(PreUpdate): + old_definition_container = DN(('cn', 'managed entries'), ('cn', 'plugins'), ('cn', 'config'), suffix) + new_definition_container = DN(('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix) + + definitions_dn = DN(('cn', 'Definitions')) + update_list = [] + restart = False + + # If the old entries don't exist the server has already been updated. + try: + (definitions_managed_entries, truncated) = ldap.find_entries( + searchfilter, ['*'], old_definition_container, _ldap.SCOPE_ONELEVEL, normalize=False + ) + except errors.NotFound, e: + return (False, update_list) + + for entry in definitions_managed_entries: + assert isinstance(entry.dn, DN) + if deletes: + old_dn = entry.data['managedtemplate'][0] + assert isinstance(old_dn, DN) + try: + (old_dn, entry) = ldap.get_entry(old_dn, ['*'], normalize=False) + except errors.NotFound, e: + pass + else: + # Compute the new dn by replacing the old container with the new container + new_dn = EditableDN(old_dn) + if new_dn.replace(old_template_container, new_template_container) != 1: + self.error("unable to replace '%s' with '%s' in '%s'", + old_template_container, new_template_container, old_dn) + continue + + new_dn = DN(new_dn) + + # The old attributes become defaults for the new entry + new_update = {'dn': new_dn, + 'default': entry_to_update(entry)} + + # Delete the old entry + old_update = {'dn': old_dn, 'deleteentry': None} + + # Add the delete and replacement updates to the list of all updates + update_list.append({old_dn: old_update, new_dn: new_update}) + + else: + # Update the template dn by replacing the old containter with the new container + old_dn = entry.data['managedtemplate'][0] + new_dn = EditableDN(old_dn) + if new_dn.replace(old_template_container, new_template_container) != 1: + self.error("unable to replace '%s' with '%s' in '%s'", + old_template_container, new_template_container, old_dn) + continue + new_dn = DN(new_dn) + entry.data['managedtemplate'] = new_dn + + # Edit the dn, then convert it back to an immutable DN + old_dn = entry.dn + new_dn = EditableDN(old_dn) + if new_dn.replace(old_definition_container, new_definition_container) != 1: + self.error("unable to replace '%s' with '%s' in '%s'", + old_definition_container, new_definition_container, old_dn) + continue + new_dn = DN(new_dn) + + # The old attributes become defaults for the new entry + new_update = {'dn': new_dn, + 'default': entry_to_update(entry.data)} + + # Add the replacement update to the collection of all updates + update_list.append({new_dn: new_update}) + + if len(update_list) > 0: + restart = True + update_list.sort(reverse=True) + + return (restart, update_list) + +class update_managed_post_first(PreUpdate, GenerateUpdateMixin): """ Update managed entries """ @@ -112,21 +145,21 @@ class update_managed_post_first(PreUpdate): def execute(self, **options): # Never need to restart with the pre-update changes - (ignore, new_managed_entries) = generate_update(self.obj.backend, False) + (ignore, update_list) = self.generate_update(False) - return (False, True, new_managed_entries) + return (False, True, update_list) api.register(update_managed_post_first) -class update_managed_post(PostUpdate): +class update_managed_post(PostUpdate, GenerateUpdateMixin): """ Update managed entries """ order=LAST def execute(self, **options): - (restart, new_managed_entries) = generate_update(self.obj.backend, True) + (restart, update_list) = self.generate_update(True) - return (restart, True, new_managed_entries) + return (restart, True, update_list) api.register(update_managed_post) diff --git a/ipaserver/install/plugins/updateclient.py b/ipaserver/install/plugins/updateclient.py index e23769471..dca2c75dd 100644 --- a/ipaserver/install/plugins/updateclient.py +++ b/ipaserver/install/plugins/updateclient.py @@ -25,6 +25,7 @@ from ipaserver.install.ldapupdate import LDAPUpdate from ipapython.ipautil import wait_for_open_socket from ipalib import api from ipalib import backend +from ipapython.dn import DN import ldap as _ldap class updateclient(backend.Executioner): @@ -44,7 +45,7 @@ class updateclient(backend.Executioner): updates is a dictionary keyed on dn. The value of an update is a dictionary with the following possible values: - - dn: str, duplicate of the key + - dn: DN, equal to the dn attribute - updates: list of updates against the dn - default: list of the default entry to be added if it doesn't exist @@ -103,7 +104,7 @@ class updateclient(backend.Executioner): autobind = False else: autobind = True - self.Backend.ldap2.connect(bind_dn='cn=Directory Manager', bind_pw=dm_password, autobind=autobind) + self.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password, autobind=autobind) def order(self, updatetype): """Return plugins of the given updatetype in sorted order. @@ -125,22 +126,12 @@ class updateclient(backend.Executioner): (restart, apply_now, res) = self.run(update.name, **kw) if restart: self.restart(dm_password, live_run) - dn_list = {} - for upd in res: - for dn in upd: - dn_explode = _ldap.explode_dn(dn.lower()) - l = len(dn_explode) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] - updates = {} - for entry in res: - updates.update(entry) if apply_now: - ld.update_from_dict(dn_list, updates) + updates = {} + for entry in res: + updates.update(entry) + ld.update_from_dict(updates) elif res: result.extend(res) diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py index 38abfe210..8fe73ca77 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -28,17 +28,16 @@ from ipapython import services as ipaservices from ldap import modlist from ipalib import api, util, errors from ipapython import ipautil -from ipalib.dn import DN +from ipapython.dn import DN -DIRMAN_CN = "cn=directory manager" CACERT = "/etc/ipa/ca.crt" # the default container used by AD for user entries -WIN_USER_CONTAINER = "cn=Users" +WIN_USER_CONTAINER = DN(('cn', 'Users')) # the default container used by IPA for user entries -IPA_USER_CONTAINER = "cn=users,cn=accounts" +IPA_USER_CONTAINER = DN(('cn', 'users'), ('cn', 'accounts')) PORT = 636 TIMEOUT = 120 -REPL_MAN_DN = "cn=replication manager,cn=config" +REPL_MAN_DN = DN(('cn', 'replication manager'), ('cn', 'config')) IPA_REPLICA = 1 WINSYNC = 2 @@ -94,9 +93,10 @@ def enable_replication_version_checking(hostname, realm, dirman_passwd): conn.do_simple_bind(bindpw=dirman_passwd) else: conn.do_sasl_gssapi_bind() - entry = conn.search_s('cn=IPA Version Replication,cn=plugins,cn=config', ldap.SCOPE_BASE, 'objectclass=*') - if entry[0].getValue('nsslapd-pluginenabled') == 'off': - conn.modify_s(entry[0].dn, [(ldap.MOD_REPLACE, 'nsslapd-pluginenabled', 'on')]) + entry = conn.getEntry(DN(('cn', 'IPA Version Replication'), ('cn', 'plugins'), ('cn', 'config')), + ldap.SCOPE_BASE, 'objectclass=*') + if entry.getValue('nsslapd-pluginenabled') == 'off': + conn.modify_s(entry.dn, [(ldap.MOD_REPLACE, 'nsslapd-pluginenabled', 'on')]) conn.unbind() serverid = "-".join(realm.split(".")) ipaservices.knownservices.dirsrv.restart(instance_name=serverid) @@ -112,8 +112,7 @@ class ReplicationManager(object): self.dirman_passwd = dirman_passwd self.realm = realm self.starttls = starttls - tmp = ipautil.realm_to_suffix(realm) - self.suffix = str(DN(tmp)).lower() + self.suffix = ipautil.realm_to_suffix(realm) self.need_memberof_fixup = False # The caller is allowed to pass in an existing IPAdmin connection. @@ -150,25 +149,28 @@ class ReplicationManager(object): """ # First see if there is already one set dn = self.replica_dn() + assert isinstance(dn, DN) try: - replica = conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] + replica = conn.getEntry(dn, ldap.SCOPE_BASE, "objectclass=*") + except errors.NotFound: + pass + else: if replica.getValue('nsDS5ReplicaId'): return int(replica.getValue('nsDS5ReplicaId')) - except ldap.NO_SUCH_OBJECT: - pass # Ok, either the entry doesn't exist or the attribute isn't set # so get it from the other master retval = -1 - dn = str(DN(('cn','replication'),('cn','etc'), self.suffix)) + dn = DN(('cn','replication'),('cn','etc'), self.suffix) try: - replica = master_conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] - if not replica.getValue('nsDS5ReplicaId'): - root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") - raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") - except ldap.NO_SUCH_OBJECT: + replica = master_conn.getEntry(dn, ldap.SCOPE_BASE, "objectclass=*") + except errors.NotFound: root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") raise + else: + if replica.getValue('nsDS5ReplicaId') is None: + root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") + raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") # Now update the value on the master retval = int(replica.getValue('nsDS5ReplicaId')) @@ -195,8 +197,9 @@ class ReplicationManager(object): """ filt = "(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement))" try: - ents = self.conn.search_s("cn=mapping tree,cn=config", ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: + ents = self.conn.getList(DN(('cn', 'mapping tree'), ('cn', 'config')), + ldap.SCOPE_SUBTREE, filt) + except errors.NotFound: ents = [] return ents @@ -212,13 +215,13 @@ class ReplicationManager(object): filt = "(objectclass=nsds5ReplicationAgreement)" try: - ents = self.conn.search_s("cn=mapping tree,cn=config", - ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: + ents = self.conn.getList(DN(('cn', 'mapping tree'), ('cn', 'config')), + ldap.SCOPE_SUBTREE, filt) + except errors.NotFound: return res for ent in ents: - res.append(ent.nsds5replicahost) + res.append(ent.getValue('nsds5replicahost')) return res @@ -234,24 +237,23 @@ class ReplicationManager(object): filt = "(&(|(objectclass=nsds5ReplicationAgreement)(objectclass=nsDSWindowsReplicationAgreement))(nsDS5ReplicaHost=%s))" % hostname try: - entry = self.conn.search_s("cn=mapping tree,cn=config", - ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: + entries = self.conn.getList(DN(('cn', 'mapping tree'), ('cn', 'config')), + ldap.SCOPE_SUBTREE, filt) + except errors.NotFound: return None - if len(entry) == 0: + if len(entries) == 0: return None else: - return entry[0] # There can be only one + return entries[0] # There can be only one def add_replication_manager(self, conn, dn, pw): """ Create a pseudo user to use for replication. """ - - edn = ldap.dn.str2dn(dn) - rdn_attr = edn[0][0][0] - rdn_val = edn[0][0][1] + assert isinstance(dn, DN) + rdn_attr = dn[0].attr + rdn_val = dn[0].value ent = ipaldap.Entry(dn) ent.setValues("objectclass", "top", "person") @@ -266,6 +268,7 @@ class ReplicationManager(object): pass def delete_replication_manager(self, conn, dn=REPL_MAN_DN): + assert isinstance(dn, DN) try: conn.delete_s(dn) except ldap.NO_SUCH_OBJECT: @@ -278,16 +281,18 @@ class ReplicationManager(object): return "2" def replica_dn(self): - return str(DN(('cn','replica'),('cn',self.suffix),('cn','mapping tree'),('cn','config'))) + return DN(('cn','replica'),('cn',self.suffix),('cn','mapping tree'),('cn','config')) def replica_config(self, conn, replica_id, replica_binddn): + assert isinstance(replica_binddn, DN) dn = self.replica_dn() + assert isinstance(dn, DN) try: entry = conn.getEntry(dn, ldap.SCOPE_BASE) managers = entry.getValues('nsDS5ReplicaBindDN') for m in managers: - if DN(replica_binddn) == DN(m): + if replica_binddn == DN(m): return # Add the new replication manager mod = [(ldap.MOD_ADD, 'nsDS5ReplicaBindDN', replica_binddn)] @@ -303,7 +308,7 @@ class ReplicationManager(object): entry = ipaldap.Entry(dn) entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject") entry.setValues('cn', "replica") - entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaroot', str(self.suffix)) entry.setValues('nsds5replicaid', str(replica_id)) entry.setValues('nsds5replicatype', replica_type) entry.setValues('nsds5flags', "1") @@ -313,7 +318,7 @@ class ReplicationManager(object): conn.addEntry(entry) def setup_changelog(self, conn): - dn = "cn=changelog5, cn=config" + dn = DN(('cn', 'changelog5'), ('cn', 'config')) dirpath = conn.dbdir + "/cldb" entry = ipaldap.Entry(dn) entry.setValues('objectclass', "top", "extensibleobject") @@ -325,7 +330,7 @@ class ReplicationManager(object): return def setup_chaining_backend(self, conn): - chaindn = "cn=chaining database, cn=plugins, cn=config" + chaindn = DN(('cn', 'chaining database'), ('cn', 'plugins'), ('cn', 'config')) benamebase = "chaindb" urls = [self.to_ldap_url(conn)] cn = "" @@ -334,11 +339,11 @@ class ReplicationManager(object): while not done: try: cn = benamebase + str(benum) # e.g. localdb1 - dn = "cn=" + cn + ", " + chaindn + dn = DN(('cn', cn), chaindn) entry = ipaldap.Entry(dn) entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance') entry.setValues('cn', cn) - entry.setValues('nsslapd-suffix', self.suffix) + entry.setValues('nsslapd-suffix', str(self.suffix)) entry.setValues('nsfarmserverurl', urls) entry.setValues('nsmultiplexorbinddn', self.repl_man_dn) entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd) @@ -365,7 +370,7 @@ class ReplicationManager(object): def get_mapping_tree_entry(self): try: - entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL, + entry = self.conn.getEntry(DN(('cn', 'mapping tree'), ('cn', 'config')), ldap.SCOPE_ONELEVEL, "(cn=\"%s\")" % (self.suffix)) except errors.NotFound, e: root_logger.debug("failed to find mappting tree entry for %s" % self.suffix) @@ -378,7 +383,7 @@ class ReplicationManager(object): mtent = self.get_mapping_tree_entry() dn = mtent.dn - plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config", + plgent = self.conn.getEntry(DN(('cn', 'Multimaster Replication Plugin'), ('cn', 'plugins'), ('cn', 'config')), ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath']) path = plgent.getValue('nsslapd-pluginPath') @@ -397,7 +402,7 @@ class ReplicationManager(object): self.enable_chain_on_update(chainbe) def add_passsync_user(self, conn, password): - pass_dn = "uid=passsync,cn=sysaccounts,cn=etc,%s" % self.suffix + pass_dn = DN(('uid', 'passsync'), ('cn', 'sysaccounts'), ('cn', 'etc'), self.suffix) print "The user for the Windows PassSync service is %s" % pass_dn try: conn.getEntry(pass_dn, ldap.SCOPE_BASE) @@ -414,7 +419,7 @@ class ReplicationManager(object): conn.addEntry(entry) # Add it to the list of users allowed to bypass password policy - extop_dn = "cn=ipa_pwd_extop,cn=plugins,cn=config" + extop_dn = DN(('cn', 'ipa_pwd_extop'), ('cn', 'plugins'), ('cn', 'config')) entry = conn.getEntry(extop_dn, ldap.SCOPE_BASE) pass_mgrs = entry.getValues('passSyncManagersDNs') if not pass_mgrs: @@ -435,9 +440,9 @@ class ReplicationManager(object): def setup_winsync_agmt(self, entry, win_subtree=None): if win_subtree is None: - win_subtree = WIN_USER_CONTAINER + "," + self.ad_suffix - ds_subtree = IPA_USER_CONTAINER + "," + self.suffix - windomain = '.'.join(ldap.explode_dn(self.suffix, 1)) + win_subtree = DN(WIN_USER_CONTAINER, self.ad_suffix) + ds_subtree = DN(IPA_USER_CONTAINER, self.suffix) + windomain = ipautil.suffix_to_realm(self.suffix) entry.setValues("objectclass", "nsDSWindowsReplicationAgreement") entry.setValues("nsds7WindowsReplicaSubtree", win_subtree) @@ -454,7 +459,7 @@ class ReplicationManager(object): tell which side we want. """ cn = "meTo%s" % (hostname) - dn = "cn=%s, %s" % (cn, self.replica_dn()) + dn = DN(('cn', cn), self.replica_dn()) return (cn, dn) @@ -469,6 +474,9 @@ class ReplicationManager(object): isn't a dogtag replication agreement. """ + if repl_man_dn is not None: + assert isinstance(repl_man_dn, DN) + cn, dn = self.agreement_dn(b_hostname, master=master) try: a_conn.getEntry(dn, ldap.SCOPE_BASE) @@ -482,7 +490,7 @@ class ReplicationManager(object): entry.setValues('nsds5replicahost', b_hostname) entry.setValues('nsds5replicaport', str(port)) entry.setValues('nsds5replicatimeout', str(TIMEOUT)) - entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaroot', str(self.suffix)) if master is None: entry.setValues('nsDS5ReplicatedAttributeList', '(objectclass=*) $ EXCLUDE %s' % " ".join(EXCLUDES)) @@ -538,10 +546,15 @@ class ReplicationManager(object): while (retries > 0 ): root_logger.info('Getting ldap service principals for conversion: %s and %s' % (filter_a, filter_b)) - a_entry = b.search_s(self.suffix, ldap.SCOPE_SUBTREE, - filterstr=filter_a) - b_entry = a.search_s(self.suffix, ldap.SCOPE_SUBTREE, - filterstr=filter_b) + try: + a_entry = b.search_s(self.suffix, ldap.SCOPE_SUBTREE, filterstr=filter_a) + except errors.NotFound: + pass + + try: + b_entry = a.search_s(self.suffix, ldap.SCOPE_SUBTREE, filterstr=filter_b) + except errors.NotFound: + pass if a_entry and b_entry: root_logger.debug('Found both principals.') @@ -578,7 +591,10 @@ class ReplicationManager(object): """ rep_dn = self.replica_dn() + assert isinstance(rep_dn, DN) (a_dn, b_dn) = self.get_replica_principal_dns(a, b, retries=10) + assert isinstance(a_dn, DN) + assert isinstance(b_dn, DN) # Add kerberos principal DNs as valid bindDNs for replication try: @@ -623,12 +639,10 @@ class ReplicationManager(object): return self.conn.deleteEntry(dn) def delete_referral(self, hostname): - esc1_suffix = self.suffix.replace('=', '\\3D').replace(',', '\\2C') - esc2_suffix = self.suffix.replace('=', '%3D').replace(',', '%2C') - dn = 'cn=%s,cn=mapping tree,cn=config' % esc1_suffix + dn = DN(('cn', self.suffix), ('cn', 'mapping tree'), ('cn', 'config')) # TODO: should we detect proto/port somehow ? mod = [(ldap.MOD_DELETE, 'nsslapd-referral', - 'ldap://%s/%s' % (ipautil.format_netloc(hostname, 389), esc2_suffix))] + 'ldap://%s/%s' % (ipautil.format_netloc(hostname, 389), self.suffix))] try: self.conn.modify_s(dn, mod) @@ -648,9 +662,9 @@ class ReplicationManager(object): print "Error reading status from agreement", agmtdn hasError = 1 else: - refresh = entry.nsds5BeginReplicaRefresh - inprogress = entry.nsds5replicaUpdateInProgress - status = entry.nsds5ReplicaLastInitStatus + refresh = entry.getValue('nsds5BeginReplicaRefresh') + inprogress = entry.getValue('nsds5replicaUpdateInProgress') + status = entry.getValue('nsds5ReplicaLastInitStatus') if not refresh: # done - check status if not status: print "No status yet" @@ -683,10 +697,10 @@ class ReplicationManager(object): print "Error reading status from agreement", agmtdn hasError = 1 else: - inprogress = entry.nsds5replicaUpdateInProgress - status = entry.nsds5ReplicaLastUpdateStatus - start = entry.nsds5ReplicaLastUpdateStart - end = entry.nsds5ReplicaLastUpdateEnd + inprogress = entry.getValue('nsds5replicaUpdateInProgress') + status = entry.getValue('nsds5ReplicaLastUpdateStatus') + start = entry.getValue('nsds5ReplicaLastUpdateStart') + end = entry.getValue('nsds5ReplicaLastUpdateEnd') # incremental update is done if inprogress is false and end >= start done = inprogress and inprogress.lower() == 'false' and start and end and (start <= end) root_logger.info("Replication Update in progress: %s: status: %s: start: %s: end: %s" % @@ -733,6 +747,7 @@ class ReplicationManager(object): return self.wait_for_repl_init(conn, dn) def basic_replication_setup(self, conn, replica_id, repldn, replpw): + assert isinstance(repldn, DN) if replpw is not None: self.add_replication_manager(conn, repldn, replpw) self.replica_config(conn, replica_id, repldn) @@ -741,6 +756,7 @@ class ReplicationManager(object): def setup_replication(self, r_hostname, r_port=389, r_sslport=636, r_binddn=None, r_bindpw=None, starttls=False, is_cs_replica=False): + assert isinstance(r_binddn, DN) # note - there appears to be a bug in python-ldap - it does not # allow connections using two different CA certs if starttls: @@ -836,7 +852,7 @@ class ReplicationManager(object): root_logger.info("Agreement is ready, starting replication . . .") # Add winsync replica to the public DIT - dn = str(DN(('cn',ad_dc_name),('cn','replicas'),('cn','ipa'),('cn','etc'), self.suffix)) + dn = DN(('cn',ad_dc_name),('cn','replicas'),('cn','ipa'),('cn','etc'), self.suffix) entry = ipaldap.Entry(dn) entry.setValues("objectclass", ["nsContainer", "ipaConfigObject"]) entry.setValues("cn", ad_dc_name) @@ -910,17 +926,17 @@ class ReplicationManager(object): filter = '(&(nsDS5ReplicaHost=%s)' \ '(|(objectclass=nsDSWindowsReplicationAgreement)' \ '(objectclass=nsds5ReplicationAgreement)))' % hostname - entry = conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) - if len(entry) == 0: + entries = conn.getList(DN(('cn', 'config')), ldap.SCOPE_SUBTREE, filter) + if len(entries) == 0: root_logger.error("Unable to find replication agreement for %s" % (hostname)) raise RuntimeError("Unable to proceed") - if len(entry) > 1: + if len(entries) > 1: root_logger.error("Found multiple agreements for %s" % hostname) - root_logger.error("Using the first one only (%s)" % entry[0].dn) + root_logger.error("Using the first one only (%s)" % entries[0].dn) - dn = entry[0].dn - schedule = entry[0].nsds5replicaupdateschedule + dn = entries[0].dn + schedule = entries[0].getValue('nsds5replicaupdateschedule') # On the remote chance of a match. We force a synch to happen right # now by setting the schedule to something and quickly removing it. @@ -965,16 +981,14 @@ class ReplicationManager(object): # delete master kerberos key and all its svc principals try: filter='(krbprincipalname=*/%s@%s)' % (replica, realm) - entries = self.conn.search_s(self.suffix, ldap.SCOPE_SUBTREE, - filterstr=filter) + entries = self.conn.getList(self.suffix, ldap.SCOPE_SUBTREE, + filterstr=filter) if len(entries) != 0: dnset = self.conn.get_dns_sorted_by_length(entries, reverse=True) for dns in dnset: for dn in dns: self.conn.deleteEntry(dn) - except ldap.NO_SUCH_OBJECT: - pass except errors.NotFound: pass except Exception, e: @@ -984,18 +998,18 @@ class ReplicationManager(object): err = e # remove replica memberPrincipal from s4u2proxy configuration - dn1 = DN(u'cn=ipa-http-delegation', api.env.container_s4u2proxy, self.suffix) + dn1 = DN(('cn', 'ipa-http-delegation'), api.env.container_s4u2proxy, self.suffix) member_principal1 = "HTTP/%(fqdn)s@%(realm)s" % dict(fqdn=replica, realm=realm) - dn2 = DN(u'cn=ipa-ldap-delegation-targets', api.env.container_s4u2proxy, self.suffix) + dn2 = DN(('cn', 'ipa-ldap-delegation-targets'), api.env.container_s4u2proxy, self.suffix) member_principal2 = "ldap/%(fqdn)s@%(realm)s" % dict(fqdn=replica, realm=realm) - dn3 = DN(u'cn=ipa-cifs-delegation-targets', api.env.container_s4u2proxy, self.suffix) + dn3 = DN(('cn', 'ipa-cifs-delegation-targets'), api.env.container_s4u2proxy, self.suffix) member_principal3 = "cifs/%(fqdn)s@%(realm)s" % dict(fqdn=replica, realm=realm) - for (dn, member_principal) in ((str(dn1), member_principal1), - (str(dn2), member_principal2), - (str(dn3), member_principal3)): + for (dn, member_principal) in ((dn1, member_principal1), + (dn2, member_principal2), + (dn3, member_principal3)): try: mod = [(ldap.MOD_DELETE, 'memberPrincipal', member_principal)] self.conn.modify_s(dn, mod) @@ -1010,16 +1024,14 @@ class ReplicationManager(object): # delete master entry with all active services try: - dn = 'cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (replica, self.suffix) - entries = self.conn.search_s(dn, ldap.SCOPE_SUBTREE) + dn = DN(('cn', replica), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), self.suffix) + entries = self.conn.getList(dn, ldap.SCOPE_SUBTREE) if len(entries) != 0: dnset = self.conn.get_dns_sorted_by_length(entries, reverse=True) for dns in dnset: for dn in dns: self.conn.deleteEntry(dn) - except ldap.NO_SUCH_OBJECT: - pass except errors.NotFound: pass except Exception, e: @@ -1029,15 +1041,13 @@ class ReplicationManager(object): err = e try: - basedn = 'cn=etc,%s' % self.suffix + basedn = DN(('cn', 'etc'), self.suffix) filter = '(dnaHostname=%s)' % replica - entries = self.conn.search_s(basedn, ldap.SCOPE_SUBTREE, - filterstr=filter) + entries = self.conn.getList(basedn, ldap.SCOPE_SUBTREE, + filterstr=filter) if len(entries) != 0: for e in entries: self.conn.deleteEntry(e.dn) - except ldap.NO_SUCH_OBJECT: - pass except errors.NotFound: pass except Exception, e: @@ -1047,18 +1057,16 @@ class ReplicationManager(object): err = e try: - dn = 'cn=default,ou=profile,%s' % self.suffix - ret = self.conn.search_s(dn, ldap.SCOPE_BASE, - '(objectclass=*)')[0] - srvlist = ret.data.get('defaultServerList') - if len(srvlist) > 0: - srvlist = srvlist[0].split() + dn = DN(('cn', 'default'), ('ou', 'profile'), self.suffix) + ret = self.conn.getEntry(dn, ldap.SCOPE_BASE, '(objectclass=*)') + srvlist = ret.getValue('defaultServerList', '') + srvlist = srvlist[0].split() if replica in srvlist: srvlist.remove(replica) attr = ' '.join(srvlist) mod = [(ldap.MOD_REPLACE, 'defaultServerList', attr)] self.conn.modify_s(dn, mod) - except ldap.NO_SUCH_OBJECT: + except errors.NotFound: pass except ldap.NO_SUCH_ATTRIBUTE: pass diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 198cb3870..dcfcbc27f 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -25,6 +25,7 @@ from ipapython import sysrestore from ipapython import ipautil from ipapython import services as ipaservices from ipalib import errors +from ipapython.dn import DN import ldap from ipaserver import ipaldap import base64 @@ -78,7 +79,7 @@ class Service(object): self.sstore = sysrestore.StateFile('/var/lib/ipa/sysrestore') self.realm = None - self.suffix = None + self.suffix = DN() self.principal = None self.dercert = None @@ -150,7 +151,7 @@ class Service(object): # use URI of admin connection if not self.admin_conn: self.ldap_connect() - args += ["-H", self.admin_conn._uri] + args += ["-H", self.admin_conn.uri] auth_parms = [] if self.dm_password: @@ -183,15 +184,15 @@ class Service(object): cn=kerberos to cn=services """ - dn = "krbprincipalname=%s,cn=%s,cn=kerberos,%s" % (principal, self.realm, self.suffix) + dn = DN(('krbprincipalname', principal), ('cn', self.realm), ('cn', 'kerberos'), self.suffix) try: entry = self.admin_conn.getEntry(dn, ldap.SCOPE_BASE) except errors.NotFound: # There is no service in the wrong location, nothing to do. # This can happen when installing a replica - return - newdn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (principal, self.suffix) - hostdn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) + return None + newdn = DN(('krbprincipalname', principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) + hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) self.admin_conn.deleteEntry(dn) entry.dn = newdn classes = entry.getValues("objectclass") @@ -211,8 +212,8 @@ class Service(object): if not self.admin_conn: self.ldap_connect() - dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (principal, self.suffix) - hostdn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) + dn = DN(('krbprincipalname', principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) + hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) entry = ipaldap.Entry(dn) entry.setValues("objectclass", ["krbprincipal", "krbprincipalaux", "krbticketpolicyaux", "ipaobject", "ipaservice", "pkiuser"]) entry.setValue("krbprincipalname", principal) @@ -242,7 +243,7 @@ class Service(object): self.ldap_disconnect() self.ldap_connect() - dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (self.principal, self.suffix) + dn = DN(('krbprincipalname', self.principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) mod = [(ldap.MOD_ADD, 'userCertificate', self.dercert)] try: self.admin_conn.modify_s(dn, mod) @@ -327,13 +328,12 @@ class Service(object): self.steps = [] def ldap_enable(self, name, fqdn, dm_password, ldap_suffix): + assert isinstance(ldap_suffix, DN) self.disable() if not self.admin_conn: self.ldap_connect() - entry_name = "cn=%s,cn=%s,%s,%s" % (name, fqdn, - "cn=masters,cn=ipa,cn=etc", - ldap_suffix) + entry_name = DN(('cn', name), ('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ldap_suffix) order = SERVICE_LIST[name][1] entry = ipaldap.Entry(entry_name) entry.setValues("objectclass", @@ -362,6 +362,8 @@ class SimpleServiceInstance(Service): self.step("configuring %s to start on boot" % self.service_name, self.__enable) self.start_creation("Configuring %s" % self.service_name) + suffix = ipautil.dn_attribute_property('_ldap_suffix') + def __start(self): self.backup_state("running", self.is_running()) self.restart() diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py index e4fa2c644..2e1b91a56 100644 --- a/ipaserver/ipaldap.py +++ b/ipaserver/ipaldap.py @@ -34,9 +34,11 @@ import ldap.sasl import ldapurl from ldap.controls import LDAPControl from ldap.ldapobject import SimpleLDAPObject +from ipapython.ipa_log_manager import * from ipapython import ipautil from ipalib import errors from ipapython.ipautil import format_netloc, wait_for_open_socket, wait_for_open_ports +from ipapython.dn import DN from ipapython.entity import Entity from ipaserver.plugins.ldap2 import IPASimpleLDAPObject @@ -92,7 +94,7 @@ class Entry: a list of values. In python-ldap, entries are returned as a list of 2-tuples. Instance variables: - * dn - string - the string DN of the entry + * dn - DN object - the DN of the entry * data - CIDict - case insensitive dict of the attributes and values """ def __init__(self,entrydata): @@ -103,13 +105,22 @@ class Entry: if isinstance(entrydata,tuple): self.dn = entrydata[0] self.data = ipautil.CIDict(entrydata[1]) - elif isinstance(entrydata,str) or isinstance(entrydata,unicode): + elif isinstance(entrydata,DN): self.dn = entrydata self.data = ipautil.CIDict() + elif isinstance(entrydata, basestring): + self.dn = DN(entrydata) + self.data = ipautil.CIDict() + else: + raise TypeError("entrydata must be 2-tuple, DN, or basestring, got %s" % type(entrydata)) else: - self.dn = '' + self.dn = DN() self.data = ipautil.CIDict() + assert isinstance(self.dn, DN) + + dn = ipautil.dn_attribute_property('_dn') + def __nonzero__(self): """This allows us to do tests like if entry: returns false if there is no data, true otherwise""" @@ -119,23 +130,16 @@ class Entry: """Return True if this entry has an attribute named name, False otherwise""" return self.data and self.data.has_key(name) - def __getattr__(self,name): - """If name is the name of an LDAP attribute, return the first value for that - attribute - equivalent to getValue - this allows the use of - entry.cn - instead of - entry.getValue('cn') - This also allows us to return None if an attribute is not found rather than - throwing an exception""" - return self.getValue(name) - def getValues(self,name): """Get the list (array) of values for the attribute named name""" return self.data.get(name) - def getValue(self,name): + def getValue(self,name, default=None): """Get the first value for the attribute named name""" - return self.data.get(name,[None])[0] + value = self.data.get(name, default) + if isinstance(value, (list, tuple)): + return value[0] + return value def setValue(self, name, *value): """ @@ -179,6 +183,7 @@ class Entry: def toDict(self): """Convert the attrs and values to a dict. The dict is keyed on the attribute name. The value is either single value or a list of values.""" + assert isinstance(self.dn, DN) result = ipautil.CIDict(self.data) for i in result.keys(): result[i] = ipautil.utf8_encode_values(result[i]) @@ -206,7 +211,7 @@ class Entry: # (1000) newdata = {} newdata.update(self.data) - ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata) + ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(str(self.dn),newdata) return sio.getvalue() class IPAdmin(IPAEntryLDAPObject): @@ -233,6 +238,7 @@ class IPAdmin(IPAEntryLDAPObject): that we can call it from places other than instance creation e.g. when we just need to reconnect """ + log_mgr.get_logger(self, True) if debug and debug.lower() == "on": ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) if cacert is not None: @@ -251,7 +257,6 @@ class IPAdmin(IPAEntryLDAPObject): self.ldapi = ldapi self.realm = realm self.suffixes = {} - self.schema = None self.__localinit() def __lateinit(self): @@ -260,7 +265,7 @@ class IPAdmin(IPAEntryLDAPObject): values. """ try: - ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config', + ent = self.getEntry(DN(('cn', 'config'), ('cn', 'ldbm database'), ('cn', 'plugins'), ('cn', 'config')), ldap.SCOPE_BASE, '(objectclass=*)', [ 'nsslapd-directory' ]) @@ -299,6 +304,9 @@ class IPAdmin(IPAEntryLDAPObject): if not isinstance(e,ldap.TIMEOUT): desc = e.args[0]['desc'].strip() info = e.args[0].get('info','').strip() + arg_desc = kw.get('arg_desc') + if arg_desc is not None: + info += " arguments: %s" % arg_desc else: desc = '' info = '' @@ -334,7 +342,7 @@ class IPAdmin(IPAEntryLDAPObject): raise errors.DatabaseError(desc=desc,info=info) def __wait_for_connection(self, timeout): - lurl = ldapurl.LDAPUrl(self._uri) + lurl = ldapurl.LDAPUrl(self.uri) if lurl.urlscheme == 'ldapi': wait_for_open_socket(lurl.hostport, timeout) else: @@ -365,25 +373,25 @@ class IPAdmin(IPAEntryLDAPObject): try: if krbccache is not None: os.environ["KRB5CCNAME"] = krbccache - self.sasl_interactive_bind_s("", SASL_AUTH) + self.sasl_interactive_bind_s(None, SASL_AUTH) self.principal = principal self.proxydn = None except ldap.LDAPError, e: self.__handle_errors(e) - def do_simple_bind(self, binddn="cn=directory manager", bindpw="", timeout=DEFAULT_TIMEOUT): - self.binddn = binddn + def do_simple_bind(self, binddn=DN(('cn', 'directory manager')), bindpw="", timeout=DEFAULT_TIMEOUT): + self.binddn = binddn # FIXME, self.binddn & self.bindpwd never referenced. self.bindpwd = bindpw self.__bind_with_wait(self.simple_bind_s, timeout, binddn, bindpw) self.__lateinit() def do_sasl_gssapi_bind(self, timeout=DEFAULT_TIMEOUT): - self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, '', SASL_AUTH) + self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, None, SASL_AUTH) self.__lateinit() def do_external_bind(self, user_name=None, timeout=DEFAULT_TIMEOUT): auth_tokens = ldap.sasl.external(user_name) - self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, '', auth_tokens) + self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, None, auth_tokens) self.__lateinit() def getEntry(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): @@ -491,13 +499,15 @@ class IPAdmin(IPAEntryLDAPObject): self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.add_s(entry.dn, entry.toTupleList()) except ldap.LDAPError, e: - arg_desc = 'entry=%s' % (entry.toTupleList()) + arg_desc = 'entry=%s: %s' % (entry.dn, entry.toTupleList()) self.__handle_errors(e, arg_desc=arg_desc) return True def updateRDN(self, dn, newrdn): """Wrap the modrdn function.""" + assert isinstance(dn, DN) + assert isinstance(newrdn, DN) sctrl = self.__get_server_controls() if dn == newrdn: @@ -516,6 +526,7 @@ class IPAdmin(IPAEntryLDAPObject): """This wraps the mod function. It assumes that the entry is already populated with all of the desired objectclasses and attributes""" + assert isinstance(dn, DN) sctrl = self.__get_server_controls() modlist = self.generateModList(oldentry, newentry) @@ -575,7 +586,7 @@ class IPAdmin(IPAEntryLDAPObject): # You can't remove schema online. An add will automatically # replace any existing schema. - if old_entry.get('dn') == 'cn=schema': + if old_entry.get('dn', DN()) == DN(('cn', 'schema')): if len(adds) > 0: modlist.append((ldap.MOD_ADD, key, adds)) else: @@ -590,11 +601,12 @@ class IPAdmin(IPAEntryLDAPObject): return modlist - def inactivateEntry(self,dn,has_key): + def inactivateEntry(self,dn, has_key): """Rather than deleting entries we mark them as inactive. has_key defines whether the entry already has nsAccountlock set so we can determine which type of mod operation to run.""" + assert isinstance(dn, DN) sctrl = self.__get_server_controls() modlist=[] @@ -616,6 +628,7 @@ class IPAdmin(IPAEntryLDAPObject): def deleteEntry(self, dn): """This wraps the delete function. Use with caution.""" + assert isinstance(dn, DN) sctrl = self.__get_server_controls() try: @@ -627,7 +640,7 @@ class IPAdmin(IPAEntryLDAPObject): self.__handle_errors(e, arg_desc=arg_desc) return True - def modifyPassword(self,dn,oldpass,newpass): + def modifyPassword(self, dn, oldpass, newpass): """Set the user password using RFC 3062, LDAP Password Modify Extended Operation. This ends up calling the IPA password slapi plugin handler so the Kerberos password gets set properly. @@ -635,6 +648,7 @@ class IPAdmin(IPAEntryLDAPObject): oldpass is not mandatory """ + assert isinstance(dn, DN) sctrl = self.__get_server_controls() try: @@ -656,6 +670,7 @@ class IPAdmin(IPAEntryLDAPObject): if isinstance(dn,Entry): dn = dn.dn + assert isinstance(dn, DN) # wait for entry and/or attr to show up if not quiet: @@ -691,6 +706,7 @@ class IPAdmin(IPAEntryLDAPObject): running, true if done - if true, second is the exit code - if dowait is True, this function will block until the task is complete """ + assert isinstance(dn, DN) attrlist = ['nsTaskLog', 'nsTaskStatus', 'nsTaskExitCode', 'nsTaskCurrentItem', 'nsTaskTotalItems'] done = False exitCode = 0 @@ -701,26 +717,13 @@ class IPAdmin(IPAEntryLDAPObject): break if verbose: print entry - if entry.nsTaskExitCode: - exitCode = int(entry.nsTaskExitCode) + if entry.getValue('nsTaskExitCode'): + exitCode = int(entry.getValue('nsTaskExitCode')) done = True if dowait: time.sleep(1) else: break return (done, exitCode) - def get_schema(self): - """ - Retrieve cn=schema and convert it into a python-ldap schema - object. - """ - if self.schema: - return self.schema - schema = self.getEntry('cn=schema', ldap.SCOPE_BASE, - '(objectclass=*)', ['attributetypes', 'objectclasses']) - schema = schema.toDict() - self.schema = ldap.schema.SubSchema(schema) - return self.schema - def get_single_value(self, attr): """ Check the schema to see if the attribute is single-valued. @@ -730,8 +733,6 @@ class IPAdmin(IPAEntryLDAPObject): If there is a problem loading the schema or the attribute is not in the schema return None """ - if not self.schema: - self.get_schema() obj = self.schema.get_obj(ldap.schema.AttributeType, attr) return obj and obj.single_value @@ -744,19 +745,27 @@ class IPAdmin(IPAEntryLDAPObject): starting from the leafs and go up to delete nodes only when all its leafs are removed. - Returns a "sorted" dict keyed by dn lengths and corresponding list - of DNs. - {'1': [dn1, dn2, dn3], '2': [dn4, dn5], ..} + Returns a list of list of dn's. Every dn in the dn list has + the same number of RDN's. The outer list is sorted according + to the number of RDN's in each inner list. + + Example: + + [['cn=bob', cn=tom], ['cn=bob,ou=people', cn=tom,ou=people]] + + dn's in list[0] have 1 RDN + dn's in list[1] have 2 RDN's """ res = dict() for e in entries: - sdn = ldap.dn.str2dn(e.dn) - l = len(sdn) - if not l in res: - res[l] = [] - res[l].append(e.dn) + dn = e.dn + assert isinstance(dn, DN) + rdn_count = len(dn) + rdn_count_list = res.setdefault(rdn_count, []) + if dn not in rdn_count_list: + rdn_count_list.append(dn) keys = res.keys() keys.sort(reverse=reverse) diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 8accb56d2..baa41ad3c 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -238,6 +238,8 @@ digits and nothing else follows. from lxml import etree import datetime +from ipapython.dn import DN +from ldap.filter import escape_filter_chars # These are general status return values used when # CMSServlet.outputError() is invoked. @@ -1239,8 +1241,8 @@ class ra(rabase.rabase): Check if a specified host is a master for a specified service. """ - base_dn = 'cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (host, api.env.basedn) - filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % service + base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) + filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % escape_filter_chars(service) try: ldap2 = self.api.Backend.ldap2 ent,trunc = ldap2.find_entries(filter=filter, base_dn=base_dn) @@ -1258,14 +1260,16 @@ class ra(rabase.rabase): Select any host which is a master for a specified service. """ - base_dn = 'cn=masters,cn=ipa,cn=etc,%s' % api.env.basedn - filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % service + base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) + filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % escape_filter_chars(service) try: ldap2 = self.api.Backend.ldap2 ent,trunc = ldap2.find_entries(filter=filter, base_dn=base_dn) if len(ent): entry = random.choice(ent) - return ldap.explode_dn(dn=entry[0],notypes=True)[1] + dn = entry[0] + assert isinstance(dn, DN) + return dn[1].value except Exception, e: pass return None diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index 6a3d2164e..a0b91fd5d 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -1,5 +1,6 @@ # Authors: # Pavel Zuna +# John Dennis # # Copyright (C) 2009 Red Hat # see file 'COPYING' for use and warranty information @@ -35,6 +36,8 @@ import tempfile import time import re import pwd +import sys +from decimal import Decimal import krbV from ipapython.ipa_log_manager import * @@ -42,6 +45,12 @@ import ldap as _ldap from ldap.ldapobject import SimpleLDAPObject import ldap.filter as _ldap_filter import ldap.sasl as _ldap_sasl +from ipapython.dn import DN, RDN +from ipapython.ipautil import CIDict +from collections import namedtuple +from ipalib.errors import NetworkError, DatabaseError + + try: from ldap.controls.simple import GetEffectiveRightsControl #pylint: disable=F0401,E0611 except ImportError: @@ -56,17 +65,26 @@ except ImportError: def __init__(self, criticality, authzId=None): LDAPControl.__init__(self, '1.3.6.1.4.1.42.2.27.9.5.2', criticality, authzId) # for backward compatibility -from ldap.functions import explode_dn -from ipalib.dn import DN from ipalib import _ import krbV from ipalib import api, errors from ipalib.crud import CrudBackend -from ipalib.encoder import Encoder, encode_args, decode_retval from ipalib.request import context +_debug_log_ldap = False + +# Make python-ldap tuple style result compatible with Entry and Entity +# objects by allowing access to the dn (tuple index 0) via the 'dn' +# attribute name and the attr dict (tuple index 1) via the 'data' +# attribute name. Thus: +# r = result[0] +# r[0] == r.dn +# r[1] == r.data +LDAPEntry = namedtuple('LDAPEntry', ['dn', 'data']) + + # Group Member types MEMBERS_ALL = 0 MEMBERS_DIRECT = 1 @@ -75,264 +93,541 @@ MEMBERS_INDIRECT = 2 # SASL authentication mechanism SASL_AUTH = _ldap_sasl.sasl({}, 'GSSAPI') -class IPASimpleLDAPObject(SimpleLDAPObject): +DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12' + +def unicode_from_utf8(val): ''' - This is a thin layer over SimpleLDAPObject which allows us to utilize - IPA specific types with the python-ldap API without the IPA caller needing - to perform the type translation, consider this a convenience layer for the - IPA programmer. + val is a UTF-8 encoded string, return a unicode object. + ''' + return val.decode('utf-8') - This subclass performs the following translations: +def value_to_utf8(val): + ''' + Coerce the val parameter to a UTF-8 encoded string representation + of the val. + ''' + + # If val is not a string we need to convert it to a string + # (specifically a unicode string). Naively we might think we need to + # call str(val) to convert to a string. This is incorrect because if + # val is already a unicode object then str() will call + # encode(default_encoding) returning a str object encoded with + # default_encoding. But we don't want to apply the default_encoding! + # Rather we want to guarantee the val object has been converted to a + # unicode string because from a unicode string we want to explicitly + # encode to a str using our desired encoding (utf-8 in this case). + # + # Note: calling unicode on a unicode object simply returns the exact + # same object (with it's ref count incremented). This means calling + # unicode on a unicode object is effectively a no-op, thus it's not + # inefficient. - * DN objects may be passed into any ldap function expecting a dn. The DN - object will be converted to a string before being passed to the python-ldap - function. This allows us to maintain DN objects as DN objects in the rest - of the code (useful for DN manipulation and DN information) and not have - to worry about conversion to a string prior to passing it ldap. + return unicode(val).encode('utf-8') +class _ServerSchema(object): + ''' + Properties of a schema retrieved from an LDAP server. ''' - def __init__(self, *args, **kwds): - SimpleLDAPObject.__init__(self, *args, **kwds) - def add(self, dn, modlist): - return SimpleLDAPObject.add(self, str(dn), modlist) + def __init__(self, server, schema): + self.server = server + self.schema = schema + self.retrieve_timestamp = time.time() - def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.add_ext(self, str(dn), modlist, serverctrls, clientctrls) +class SchemaCache(object): + ''' + Cache the schema's from individual LDAP servers. + ''' - def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.add_ext_s(self, str(dn), modlist, serverctrls, clientctrls) + def __init__(self): + log_mgr.get_logger(self, True) + self.servers = {} - def add_s(self, dn, modlist): - return SimpleLDAPObject.add_s(self, str(dn), modlist) + def get_schema(self, url, conn=None, force_update=False): + ''' + Return schema belonging to a specific LDAP server. - def compare(self, dn, attr, value): - return SimpleLDAPObject.compare(self, str(dn), attr, value) + For performance reasons the schema is retrieved once and + cached unless force_update is True. force_update flushes the + existing schema for the server from the cache and reacquires + it. + ''' - def compare_ext(self, dn, attr, value, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.compare_ext(self, str(dn), attr, value, serverctrls, clientctrls) + if force_update: + self.flush(url) - def compare_ext_s(self, dn, attr, value, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.compare_ext_s(self, str(dn), attr, value, serverctrls, clientctrls) + server_schema = self.servers.get(url) + if server_schema is None: + schema = self._retrieve_schema_from_server(url, conn) + server_schema = _ServerSchema(url, schema) + self.servers[url] = server_schema + return server_schema.schema - def compare_s(self, dn, attr, value): - return SimpleLDAPObject.compare_s(self, str(dn), attr, value) + def flush(self, url): + self.debug('flushing %s from SchemaCache', url) + try: + del self.servers[url] + except KeyError: + pass - def delete(self, dn): - return SimpleLDAPObject.delete(self, str(dn)) + def _retrieve_schema_from_server(self, url, conn=None): + """ + Retrieve the LDAP schema from the provided url and determine if + User-Private Groups (upg) are configured. - def delete_ext(self, dn, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.delete_ext(self, str(dn), serverctrls, clientctrls) + Bind using kerberos credentials. If in the context of the + in-tree "lite" server then use the current ccache. If in the context of + Apache then create a new ccache and bind using the Apache HTTP service + principal. - def delete_ext_s(self, dn, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.delete_ext_s(self, str(dn), serverctrls, clientctrls) + If a connection is provided then it the credentials bound to it are + used. The connection is not closed when the request is done. + """ + tmpdir = None + has_conn = conn is not None - def delete_s(self, dn): - return SimpleLDAPObject.delete_s(self, str(dn)) + self.debug('retrieving schema for SchemaCache url=%s conn=%s', url, conn) - def modify(self, dn, modlist): - return SimpleLDAPObject.modify(self, str(dn), modlist) + try: + if api.env.context == 'server' and conn is None: + # FIXME: is this really what we want to do? + # This seems like this logic is in the wrong place and may conflict with other state. + try: + # Create a new credentials cache for this Apache process + tmpdir = tempfile.mkdtemp(prefix = "tmp-") + ccache_file = 'FILE:%s/ccache' % tmpdir + krbcontext = krbV.default_context() + principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm)) + keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext) + principal = krbV.Principal(name=principal, context=krbcontext) + prev_ccache = os.environ.get('KRB5CCNAME') + os.environ['KRB5CCNAME'] = ccache_file + ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal) + ccache.init(principal) + ccache.init_creds_keytab(keytab=keytab, principal=principal) + except krbV.Krb5Error, e: + raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e))) + finally: + if prev_ccache is not None: + os.environ['KRB5CCNAME'] = prev_ccache + + + if conn is None: + conn = IPASimpleLDAPObject(url) + if url.startswith('ldapi://'): + conn.set_option(_ldap.OPT_HOST_NAME, api.env.host) + conn.sasl_interactive_bind_s(None, SASL_AUTH) + + schema_entry = conn.search_s('cn=schema', _ldap.SCOPE_BASE, + attrlist=['attributetypes', 'objectclasses'])[0] + if not has_conn: + conn.unbind_s() + except _ldap.SERVER_DOWN: + raise NetworkError(uri=url, + error=u'LDAP Server Down, unable to retrieve LDAP schema') + except _ldap.LDAPError, e: + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info', '').strip() + raise DatabaseError(desc = u'uri=%s' % url, + info = u'Unable to retrieve LDAP schema: %s: %s' % (desc, info)) + except IndexError: + # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema' + # TODO: DS uses 'cn=schema', support for other server? + # raise a more appropriate exception + raise + finally: + if tmpdir: + shutil.rmtree(tmpdir) + + return _ldap.schema.SubSchema(schema_entry[1]) + +schema_cache = SchemaCache() + +class IPASimpleLDAPObject(object): + ''' + The purpose of this class is to provide a boundary between IPA and + python-ldap. In IPA we use IPA defined types because they are + richer and are designed to meet our needs. We also require that we + consistently use those types without exception. On the other hand + python-ldap uses different types. The goal is to be able to have + IPA code call python-ldap methods using the types native to + IPA. This class accomplishes that goal by exposing python-ldap + methods which take IPA types, convert them to python-ldap types, + call python-ldap, and then convert the results returned by + python-ldap into IPA types. + + IPA code should never call python-ldap directly, it should only + call python-ldap methods in this class. + ''' - def modify_ext(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.modify_ext(self, str(dn), modlist, serverctrls, clientctrls) + # Note: the oid for dn syntax is: 1.3.6.1.4.1.1466.115.121.1.12 - def modify_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.modify_ext_s(self, str(dn), modlist, serverctrls, clientctrls) + _SYNTAX_MAPPING = { + '1.3.6.1.4.1.1466.115.121.1.1' : str, # ACI item + '1.3.6.1.4.1.1466.115.121.1.4' : str, # Audio + '1.3.6.1.4.1.1466.115.121.1.5' : str, # Binary + '1.3.6.1.4.1.1466.115.121.1.8' : str, # Certificate + '1.3.6.1.4.1.1466.115.121.1.9' : str, # Certificate List + '1.3.6.1.4.1.1466.115.121.1.10' : str, # Certificate Pair + '1.3.6.1.4.1.1466.115.121.1.23' : str, # Fax + '1.3.6.1.4.1.1466.115.121.1.28' : str, # JPEG + '1.3.6.1.4.1.1466.115.121.1.40' : str, # OctetString (same as Binary) + '1.3.6.1.4.1.1466.115.121.1.49' : str, # Supported Algorithm + '1.3.6.1.4.1.1466.115.121.1.51' : str, # Teletext Terminal Identifier + + DN_SYNTAX_OID : DN, # DN, member, memberof + '2.16.840.1.113730.3.8.3.3' : DN, # enrolledBy + '2.16.840.1.113730.3.8.3.18' : DN, # managedBy + '2.16.840.1.113730.3.8.3.5' : DN, # memberUser + '2.16.840.1.113730.3.8.3.7' : DN, # memberHost + '2.16.840.1.113730.3.8.3.20' : DN, # memberService + '2.16.840.1.113730.3.8.11.4' : DN, # ipaNTFallbackPrimaryGroup + '2.16.840.1.113730.3.8.11.21' : DN, # ipaAllowToImpersonate + '2.16.840.1.113730.3.8.11.22' : DN, # ipaAllowedTarget + '2.16.840.1.113730.3.8.7.1' : DN, # memberAllowCmd + '2.16.840.1.113730.3.8.7.2' : DN, # memberDenyCmd + + '2.16.840.1.113719.1.301.4.14.1' : DN, # krbRealmReferences + '2.16.840.1.113719.1.301.4.17.1' : DN, # krbKdcServers + '2.16.840.1.113719.1.301.4.18.1' : DN, # krbPwdServers + '2.16.840.1.113719.1.301.4.26.1' : DN, # krbPrincipalReferences + '2.16.840.1.113719.1.301.4.29.1' : DN, # krbAdmServers + '2.16.840.1.113719.1.301.4.36.1' : DN, # krbPwdPolicyReference + '2.16.840.1.113719.1.301.4.40.1' : DN, # krbTicketPolicyReference + '2.16.840.1.113719.1.301.4.41.1' : DN, # krbSubTrees + '2.16.840.1.113719.1.301.4.52.1' : DN, # krbObjectReferences + '2.16.840.1.113719.1.301.4.53.1' : DN, # krbPrincContainerRef + } - def modify_s(self, dn, modlist): - return SimpleLDAPObject.modify_s(self, str(dn), modlist) + # In most cases we lookup the syntax from the schema returned by + # the server. However, sometimes attributes may not be defined in + # the schema (e.g. extensibleObject which permits undefined + # attributes), or the schema was incorrectly defined (i.e. giving + # an attribute the syntax DirectoryString when in fact it's really + # a DN). This (hopefully sparse) table allows us to trap these + # anomalies and force them to be the syntax we know to be in use. + # + # FWIW, many entries under cn=config are undefined :-( + + _SCHEMA_OVERRIDE = CIDict({ + 'managedtemplate': DN_SYNTAX_OID, # DN + 'managedbase': DN_SYNTAX_OID, # DN + 'originscope': DN_SYNTAX_OID, # DN + }) + + def __init__(self, uri): + log_mgr.get_logger(self, True) + self.uri = uri + self.conn = SimpleLDAPObject(uri) + self._schema = None + + def _get_schema(self): + if self._schema is None: + # The schema may be updated during install or during + # updates, make sure we have a current version of the + # schema, not an out of date cached version. + force_update = api.env.context in ('installer', 'updates') + self._schema = schema_cache.get_schema(self.uri, self.conn, force_update=force_update) + return self._schema + + schema = property(_get_schema, None, None, 'schema associated with this LDAP server') + + + def flush_cached_schema(self): + ''' + Force this instance to forget it's cached schema and reacquire + it from the schema cache. + ''' + + # Currently this is called during bind operations to assure + # we're working with valid schema for a specific + # connection. This causes self._get_schema() to query the + # schema cache for the server's schema passing along a flag + # indicating if we're in a context that requires freshly + # loading the schema vs. returning the last cached version of + # the schema. If we're in a mode that permits use of + # previously cached schema the flush and reacquire is a very + # low cost operation. + # + # The schema is reacquired whenever this object is + # instantiated or when binding occurs. The schema is not + # reacquired for operations during a bound connection, it is + # presumed schema cannot change during this interval. This + # provides for maximum efficiency in contexts which do need + # schema refreshing by only peforming the refresh inbetween + # logical operations that have the potential to cause a schema + # change. + + self._schema = None + + def get_syntax(self, attr): + # Is this a special case attribute? + syntax = self._SCHEMA_OVERRIDE.get(attr) + if syntax is not None: + return syntax + + # Try to lookup the syntax in the schema returned by the server + obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) + if obj is not None: + return obj.syntax + else: + return None - def modrdn(self, dn, newrdn, delold=1): - return SimpleLDAPObject.modrdn(self, str(dn), str(newrdn), delold) + def has_dn_syntax(self, attr): + """ + Check the schema to see if the attribute uses DN syntax. - def modrdn_s(self, dn, newrdn, delold=1): - return SimpleLDAPObject.modrdn_s(self, str(dn), str(newrdn), delold) + Returns True/False + """ + syntax = self.get_syntax(attr) + return syntax == DN_SYNTAX_OID + + + def encode(self, val): + ''' + ''' + # Booleans are both an instance of bool and int, therefore + # test for bool before int otherwise the int clause will be + # entered for a boolean value instead of the boolean clause. + if isinstance(val, bool): + if val: + return 'TRUE' + else: + return 'FALSE' + elif isinstance(val, (unicode, float, int, long, Decimal, DN)): + return value_to_utf8(val) + elif isinstance(val, str): + return val + elif isinstance(val, list): + return [self.encode(m) for m in val] + elif isinstance(val, tuple): + return tuple(self.encode(m) for m in val) + elif isinstance(val, dict): + dct = dict((self.encode(k), self.encode(v)) for k, v in val.iteritems()) + return dct + elif val is None: + return None + else: + raise TypeError("attempt to pass unsupported type to ldap, value=%s type=%s" %(val, type(val))) - def read_subschemasubentry_s(self, subschemasubentry_dn, attrs=None): - return SimpleLDAPObject.read_subschemasubentry_s(self, str(subschemasubentry_dn), attrs) + def convert_value_list(self, attr, target_type, values): + ''' + ''' - def rename(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.rename(self, str(dn), str(newrdn), newsuperior, delold, serverctrls, clientctrls) + ipa_values = [] - def rename_s(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.rename_s(self, str(dn), str(newrdn), newsuperior, delold, serverctrls, clientctrls) + for original_value in values: + if isinstance(target_type, type) and isinstance(original_value, target_type): + ipa_value = original_value + else: + try: + ipa_value = target_type(original_value) + except Exception, e: + msg = 'unable to convert the attribute "%s" value "%s" to type %s' % (attr, original_value, target_type) + self.error(msg) + raise ValueError(msg) - def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): - return SimpleLDAPObject.search(self, str(base), scope, filterstr, attrlist, attrsonly) + ipa_values.append(ipa_value) - def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, - serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): - return SimpleLDAPObject.search_ext(self, str(base), scope, filterstr, attrlist, attrsonly, - serverctrls, clientctrls, timeout, sizelimit) + return ipa_values - def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, - serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): - return SimpleLDAPObject.search_ext_s(self, str(base), scope, filterstr, attrlist, attrsonly, - serverctrls, clientctrls, timeout, sizelimit) + def convert_result(self, result): + ''' + result is a python-ldap result tuple of the form (dn, attrs), + where dn is a string containing the dn (distinguished name) of + the entry, and attrs is a dictionary containing the attributes + associated with the entry. The keys of attrs are strings, and + the associated values are lists of strings. - def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): - return SimpleLDAPObject.search_s(self, str(base), scope, filterstr, attrlist, attrsonly) + We convert the dn to a DN object. - def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1): - return SimpleLDAPObject.search_st(self, str(base), scope, filterstr, attrlist, attrsonly, timeout) + We convert every value associated with an attribute according + to it's syntax into the desired Python type. - def search_subschemasubentry_s(self, dn=''): - return SimpleLDAPObject.search_subschemasubentry_s(self, str(dn)) + returns a IPA result tuple of the same form as a python-ldap + result tuple except everything inside of the result tuple has + been converted to it's preferred IPA python type. + ''' -# universal LDAPError handler -def _handle_errors(e, **kw): - """ - Centralize error handling in one place. + ipa_result = [] + for dn_tuple in result: + original_dn = dn_tuple[0] + original_attrs = dn_tuple[1] - e is the error to be raised - **kw is an exception-specific list of options - """ - if not isinstance(e, _ldap.TIMEOUT): - desc = e.args[0]['desc'].strip() - info = e.args[0].get('info', '').strip() - else: - desc = '' - info = '' - - try: - # re-raise the error so we can handle it - raise e - except _ldap.NO_SUCH_OBJECT: - # args = kw.get('args', '') - # raise errors.NotFound(msg=notfound(args)) - raise errors.NotFound(reason='no such entry') - except _ldap.ALREADY_EXISTS: - raise errors.DuplicateEntry() - except _ldap.CONSTRAINT_VIOLATION: - # This error gets thrown by the uniqueness plugin - if info.startswith('Another entry with the same attribute value already exists'): - raise errors.DuplicateEntry() - else: - raise errors.DatabaseError(desc=desc, info=info) - except _ldap.INSUFFICIENT_ACCESS: - raise errors.ACIError(info=info) - except _ldap.INVALID_CREDENTIALS: - raise errors.ACIError(info="%s %s" % (info, desc)) - except _ldap.NO_SUCH_ATTRIBUTE: - # this is raised when a 'delete' attribute isn't found. - # it indicates the previous attribute was removed by another - # update, making the oldentry stale. - raise errors.MidairCollision() - except _ldap.INVALID_SYNTAX: - raise errors.InvalidSyntax(attr=info) - except _ldap.OBJECT_CLASS_VIOLATION: - raise errors.ObjectclassViolation(info=info) - except _ldap.ADMINLIMIT_EXCEEDED: - raise errors.LimitsExceeded() - except _ldap.SIZELIMIT_EXCEEDED: - raise errors.LimitsExceeded() - except _ldap.TIMELIMIT_EXCEEDED: - raise errors.LimitsExceeded() - except _ldap.NOT_ALLOWED_ON_RDN: - raise errors.NotAllowedOnRDN(attr=info) - except _ldap.FILTER_ERROR: - raise errors.BadSearchFilter(info=info) - except _ldap.SUCCESS: - pass - except _ldap.LDAPError, e: - if 'NOT_ALLOWED_TO_DELEGATE' in info: - raise errors.ACIError(info="KDC returned NOT_ALLOWED_TO_DELEGATE") - root_logger.info('Unhandled LDAPError: %s' % str(e)) - raise errors.DatabaseError(desc=desc, info=info) - - -def get_schema(url, conn=None): - """ - Perform global initialization when the module is loaded. + ipa_dn = DN(original_dn) + ipa_attrs = dict() - Retrieve the LDAP schema from the provided url and determine if - User-Private Groups (upg) are configured. + for attr, original_values in original_attrs.items(): + target_type = self._SYNTAX_MAPPING.get(self.get_syntax(attr), unicode_from_utf8) + ipa_attrs[attr.lower()] = self.convert_value_list(attr, target_type, original_values) - Bind using kerberos credentials. If in the context of the - in-tree "lite" server then use the current ccache. If in the context of - Apache then create a new ccache and bind using the Apache HTTP service - principal. + ipa_result.append(LDAPEntry(ipa_dn, ipa_attrs)) - If a connection is provided then it the credentials bound to it are - used. The connection is not closed when the request is done. - """ - tmpdir = None - has_conn = conn is not None + if _debug_log_ldap: + self.debug('ldap.result: %s', ipa_result) + return ipa_result - if ((not api.env.in_server or api.env.context not in ['lite', 'server']) - and conn is None): - # The schema is only needed on the server side - return None + #---------- python-ldap emulations ---------- - try: - if api.env.context == 'server' and conn is None: - try: - # Create a new credentials cache for this Apache process - tmpdir = tempfile.mkdtemp(prefix = "tmp-") - ccache_file = 'FILE:%s/ccache' % tmpdir - krbcontext = krbV.default_context() - principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm)) - keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext) - principal = krbV.Principal(name=principal, context=krbcontext) - os.environ['KRB5CCNAME'] = ccache_file - ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal) - ccache.init(principal) - ccache.init_creds_keytab(keytab=keytab, principal=principal) - except krbV.Krb5Error, e: - raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e))) - - if conn is None: - conn = IPASimpleLDAPObject(url) - if url.startswith('ldapi://'): - conn.set_option(_ldap.OPT_HOST_NAME, api.env.host) - conn.sasl_interactive_bind_s('', SASL_AUTH) - - schema_entry = conn.search_s( - 'cn=schema', _ldap.SCOPE_BASE, - attrlist=['attributetypes', 'objectclasses'] - )[0] - if not has_conn: - conn.unbind_s() - except _ldap.SERVER_DOWN: - return None - except _ldap.LDAPError, e: - desc = e.args[0]['desc'].strip() - info = e.args[0].get('info', '').strip() - raise StandardError('Unable to retrieve LDAP schema: %s: %s' % (desc, info)) - except IndexError: - # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema' - # TODO: DS uses 'cn=schema', support for other server? - # raise a more appropriate exception - raise - finally: - if tmpdir: - shutil.rmtree(tmpdir) - - return _ldap.schema.SubSchema(schema_entry[1]) - -# Global schema -_schema = None - -class ldap2(CrudBackend, Encoder): + def add(self, dn, modlist): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add(dn, modlist) + + def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add_ext(dn, modlist, serverctrls, clientctrls) + + def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add_ext_s(dn, modlist, serverctrls, clientctrls) + + def add_s(self, dn, modlist): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add_s(dn, modlist) + + def bind(self, who, cred, method=_ldap.AUTH_SIMPLE): + self.flush_cached_schema() + if who is None: + who = DN() + assert isinstance(who, DN) + who = str(who) + cred = self.encode(cred) + return self.conn.bind(who, cred, method) + + def delete(self, dn): + assert isinstance(dn, DN) + dn = str(dn) + return self.conn.delete(dn) + + def delete_s(self, dn): + assert isinstance(dn, DN) + dn = str(dn) + return self.conn.delete_s(dn) + + def get_option(self, option): + return self.conn.get_option(option) + + def modify_s(self, dn, modlist): + assert isinstance(dn, DN) + dn = str(dn) + modlist = [(x[0], self.encode(x[1]), self.encode(x[2])) for x in modlist] + return self.conn.modify_s(dn, modlist) + + def modrdn_s(self, dn, newrdn, delold=1): + assert isinstance(dn, DN) + dn = str(dn) + assert isinstance(newrdn, (DN, RDN)) + newrdn = str(newrdn) + return self.conn.modrdn_s(dn, newrdn, delold) + + def passwd_s(self, dn, oldpw, newpw, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + oldpw = self.encode(oldpw) + newpw = self.encode(newpw) + return self.conn.passwd_s(dn, oldpw, newpw, serverctrls, clientctrls) + + def rename_s(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + assert isinstance(newrdn, (DN, RDN)) + newrdn = str(newrdn) + return self.conn.rename_s(dn, newrdn, newsuperior, delold, serverctrls, clientctrls) + + def result(self, msgid=_ldap.RES_ANY, all=1, timeout=None): + resp_type, resp_data = self.conn.result(msgid, all, timeout) + resp_data = self.convert_result(resp_data) + return resp_type, resp_data + + def sasl_interactive_bind_s(self, who, auth, serverctrls=None, clientctrls=None, sasl_flags=_ldap.SASL_QUIET): + self.flush_cached_schema() + if who is None: + who = DN() + assert isinstance(who, DN) + who = str(who) + return self.conn.sasl_interactive_bind_s(who, auth, serverctrls, clientctrls, sasl_flags) + + def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + return self.conn.search(base, scope, filterstr, attrlist, attrsonly) + + def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + + if _debug_log_ldap: + self.debug("ldap.search_ext: dn: %s\nfilter: %s\nattrs_list: %s", base, filterstr, attrlist) + + + return self.conn.search_ext(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit) + + def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + ldap_result = self.conn.search_ext_s(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit) + ipa_result = self.convert_result(ldap_result) + return ipa_result + + def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + ldap_result = self.conn.search_s(base, scope, filterstr, attrlist, attrsonly) + ipa_result = self.convert_result(ldap_result) + return ipa_result + + def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + ldap_result = self.conn.search_st(base, scope, filterstr, attrlist, attrsonly, timeout) + ipa_result = self.convert_result(ldap_result) + return ipa_result + + def set_option(self, option, invalue): + return self.conn.set_option(option, invalue) + + def simple_bind_s(self, who=None, cred='', serverctrls=None, clientctrls=None): + self.flush_cached_schema() + if who is None: + who = DN() + assert isinstance(who, DN) + who = str(who) + cred = self.encode(cred) + return self.conn.simple_bind_s(who, cred, serverctrls, clientctrls) + + def start_tls_s(self): + return self.conn.start_tls_s() + + def unbind(self): + self.flush_cached_schema() + return self.conn.unbind() + + def unbind_s(self): + self.flush_cached_schema() + return self.conn.unbind_s() + +class ldap2(CrudBackend): """ LDAP Backend Take 2. """ - # attribute syntax to python type mapping, 'SYNTAX OID': type - # everything not in this dict is considered human readable unicode - _SYNTAX_MAPPING = { - '1.3.6.1.4.1.1466.115.121.1.1': str, # ACI item - '1.3.6.1.4.1.1466.115.121.1.4': str, # Audio - '1.3.6.1.4.1.1466.115.121.1.5': str, # Binary - '1.3.6.1.4.1.1466.115.121.1.8': str, # Certificate - '1.3.6.1.4.1.1466.115.121.1.9': str, # Certificate List - '1.3.6.1.4.1.1466.115.121.1.10': str, # Certificate Pair - '1.3.6.1.4.1.1466.115.121.1.23': str, # Fax - '1.3.6.1.4.1.1466.115.121.1.28': str, # JPEG - '1.3.6.1.4.1.1466.115.121.1.40': str, # OctetString (same as Binary) - '1.3.6.1.4.1.1466.115.121.1.49': str, # Supported Algorithm - '1.3.6.1.4.1.1466.115.121.1.51': str, # Teletext Terminal Identifier - } - # attributes in this list cannot be deleted by update_entry # only MOD_REPLACE operations are generated for them _FORCE_REPLACE_ON_UPDATE_ATTRS = [] @@ -349,27 +644,19 @@ class ldap2(CrudBackend, Encoder): def __init__(self, shared_instance=True, ldap_uri=None, base_dn=None, schema=None): - global _schema + log_mgr.get_logger(self, True) CrudBackend.__init__(self, shared_instance=shared_instance) - Encoder.__init__(self) - self.encoder_settings.encode_dict_keys = True - self.encoder_settings.decode_dict_keys = True - self.encoder_settings.decode_dict_vals_postprocess = False - self.encoder_settings.decode_dict_vals_table = self._SYNTAX_MAPPING - self.encoder_settings.decode_dict_vals_table_keygen = self.get_syntax - self.encoder_settings.decode_postprocessor = lambda x: string.lower(x) try: self.ldap_uri = ldap_uri or api.env.ldap_uri except AttributeError: self.ldap_uri = 'ldap://example.com' try: if base_dn is not None: - self.base_dn = base_dn + self.base_dn = DN(base_dn) else: - self.base_dn = api.env.basedn + self.base_dn = DN(api.env.basedn) except AttributeError: - self.base_dn = '' - self.schema = schema or _schema + self.base_dn = DN() def __del__(self): if self.isconnected(): @@ -378,18 +665,83 @@ class ldap2(CrudBackend, Encoder): def __str__(self): return self.ldap_uri + def _get_schema(self): + return self.conn.schema + schema = property(_get_schema, None, None, 'schema associated with this LDAP server') + + # universal LDAPError handler + def handle_errors(self, e): + """ + Centralize error handling in one place. + + e is the error to be raised + """ + if not isinstance(e, _ldap.TIMEOUT): + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info', '').strip() + else: + desc = '' + info = '' + + try: + # re-raise the error so we can handle it + raise e + except _ldap.NO_SUCH_OBJECT: + raise errors.NotFound(reason='no such entry') + except _ldap.ALREADY_EXISTS: + raise errors.DuplicateEntry() + except _ldap.CONSTRAINT_VIOLATION: + # This error gets thrown by the uniqueness plugin + if info.startswith('Another entry with the same attribute value already exists'): + raise errors.DuplicateEntry() + else: + raise errors.DatabaseError(desc=desc, info=info) + except _ldap.INSUFFICIENT_ACCESS: + raise errors.ACIError(info=info) + except _ldap.INVALID_CREDENTIALS: + raise errors.ACIError(info="%s %s" % (info, desc)) + except _ldap.NO_SUCH_ATTRIBUTE: + # this is raised when a 'delete' attribute isn't found. + # it indicates the previous attribute was removed by another + # update, making the oldentry stale. + raise errors.MidairCollision() + except _ldap.INVALID_SYNTAX: + raise errors.InvalidSyntax(attr=info) + except _ldap.OBJECT_CLASS_VIOLATION: + raise errors.ObjectclassViolation(info=info) + except _ldap.ADMINLIMIT_EXCEEDED: + raise errors.LimitsExceeded() + except _ldap.SIZELIMIT_EXCEEDED: + raise errors.LimitsExceeded() + except _ldap.TIMELIMIT_EXCEEDED: + raise errors.LimitsExceeded() + except _ldap.NOT_ALLOWED_ON_RDN: + raise errors.NotAllowedOnRDN(attr=info) + except _ldap.FILTER_ERROR: + raise errors.BadSearchFilter(info=info) + except _ldap.SUCCESS: + pass + except _ldap.LDAPError, e: + if 'NOT_ALLOWED_TO_DELEGATE' in info: + raise errors.ACIError(info="KDC returned NOT_ALLOWED_TO_DELEGATE") + self.info('Unhandled LDAPError: %s' % str(e)) + raise errors.DatabaseError(desc=desc, info=info) + def get_syntax(self, attr, value): - if not self.schema: - self.get_schema() + if self.schema is None: + return None obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) if obj is not None: return obj.syntax else: return None + def has_dn_syntax(self, attr): + return self.conn.has_dn_syntax(attr) + def get_allowed_attributes(self, objectclasses, raise_on_unknown=False): - if not self.schema: - self.get_schema() + if self.schema is None: + return None allowed_attributes = [] for oc in objectclasses: obj = self.schema.get_obj(_ldap.schema.ObjectClass, oc) @@ -408,13 +760,12 @@ class ldap2(CrudBackend, Encoder): If there is a problem loading the schema or the attribute is not in the schema return None """ - if not self.schema: - self.get_schema() + if self.schema is None: + return None obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) return obj and obj.single_value - @encode_args(2, 3, 'bind_dn', 'bind_pw') - def create_connection(self, ccache=None, bind_dn='', bind_pw='', + def create_connection(self, ccache=None, bind_dn=None, bind_pw='', tls_cacertfile=None, tls_certfile=None, tls_keyfile=None, debug_level=0, autobind=False): """ @@ -433,7 +784,9 @@ class ldap2(CrudBackend, Encoder): Extends backend.Connectible.create_connection. """ - global _schema + if bind_dn is None: + bind_dn = DN() + assert isinstance(bind_dn, DN) if tls_cacertfile is not None: _ldap.set_option(_ldap.OPT_X_TLS_CACERTFILE, tls_cacertfile) if tls_certfile is not None: @@ -445,7 +798,7 @@ class ldap2(CrudBackend, Encoder): _ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level) try: - conn = _ldap.initialize(self.ldap_uri) + conn = IPASimpleLDAPObject(self.ldap_uri) if self.ldap_uri.startswith('ldapi://') and ccache: conn.set_option(_ldap.OPT_HOST_NAME, api.env.host) minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN) @@ -459,7 +812,7 @@ class ldap2(CrudBackend, Encoder): conn.set_option(_ldap.OPT_X_SASL_SSF_MAX, minssf) if ccache is not None: os.environ['KRB5CCNAME'] = ccache - conn.sasl_interactive_bind_s('', SASL_AUTH) + conn.sasl_interactive_bind_s(None, SASL_AUTH) principal = krbV.CCache(name=ccache, context=krbV.default_context()).principal().name setattr(context, 'principal', principal) @@ -468,15 +821,13 @@ class ldap2(CrudBackend, Encoder): if autobind: pent = pwd.getpwuid(os.geteuid()) auth_tokens = _ldap.sasl.external(pent.pw_name) - conn.sasl_interactive_bind_s("", auth_tokens) + conn.sasl_interactive_bind_s(None, auth_tokens) else: conn.simple_bind_s(bind_dn, bind_pw) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) - if _schema: - object.__setattr__(self, 'schema', _schema) return conn def destroy_connection(self): @@ -489,55 +840,41 @@ class ldap2(CrudBackend, Encoder): def normalize_dn(self, dn): """ - Normalize distinguished name. + Normalize distinguished name by assuring it ends with + the base_dn. Note: You don't have to normalize DN's before passing them to ldap2 methods. It's done internally for you. """ - rdns = explode_dn(dn) - if rdns: - dn = ','.join(rdns) - if not dn.endswith(self.base_dn): - dn = '%s,%s' % (dn, self.base_dn) - return dn - return self.base_dn - - def get_container_rdn(self, name): - """Get relative distinguished name of cotainer.""" - env_container = 'container_%s' % name - if env_container in self.api.env: - return self.api.env[env_container] - return '' - def make_rdn_from_attr(self, attr, value): - """Make relative distinguished name from attribute.""" - if isinstance(value, (list, tuple)): - value = value[0] - attr = _ldap.dn.escape_dn_chars(attr) - value = _ldap.dn.escape_dn_chars(value) - return '%s=%s' % (attr, value) + assert isinstance(dn, DN) - def make_dn_from_rdn(self, rdn, parent_dn=''): - """ - Make distinguished name from relative distinguished name. + if not dn.endswith(self.base_dn): + # DN's are mutable, don't use in-place addtion (+=) which would + # modify the dn passed in with unintended side-effects. Addition + # returns a new DN object which is the concatenation of the two. + dn = dn + self.base_dn - Keyword arguments: - parent_dn -- DN of the parent entry (default '') - """ - parent_dn = self.normalize_dn(parent_dn) - return '%s,%s' % (rdn, parent_dn) + return dn - def make_dn_from_attr(self, attr, value, parent_dn=''): + def make_dn_from_attr(self, attr, value, parent_dn=None): """ Make distinguished name from attribute. Keyword arguments: parent_dn -- DN of the parent entry (default '') """ - rdn = self.make_rdn_from_attr(attr, value) - return self.make_dn_from_rdn(rdn, parent_dn) + if parent_dn is None: + parent_dn = DN() + assert isinstance(parent_dn, DN) + parent_dn = self.normalize_dn(parent_dn) + + if isinstance(value, (list, tuple)): + value = value[0] + + return DN((attr, value), parent_dn) - def make_dn(self, entry_attrs, primary_key='cn', parent_dn=''): + def make_dn(self, entry_attrs, primary_key='cn', parent_dn=None): """ Make distinguished name from entry attributes. @@ -545,23 +882,32 @@ class ldap2(CrudBackend, Encoder): primary_key -- attribute from which to make RDN (default 'cn') parent_dn -- DN of the parent entry (default '') """ + assert primary_key in entry_attrs - rdn = self.make_rdn_from_attr(primary_key, entry_attrs[primary_key]) - return self.make_dn_from_rdn(rdn, parent_dn) - @encode_args(1, 2) + if parent_dn is None: + parent_dn = DN() + + parent_dn = self.normalize_dn(parent_dn) + return DN((primary_key, entry_attrs[primary_key]), parent_dn) + def add_entry(self, dn, entry_attrs, normalize=True): """Create a new entry.""" + + assert isinstance(dn, DN) + if normalize: dn = self.normalize_dn(dn) - # remove all None values, python-ldap hates'em + # remove all None or [] values, python-ldap hates'em entry_attrs = dict( - (k, v) for (k, v) in entry_attrs.iteritems() if v + # FIXME, shouldn't these values be an error? + (k, v) for (k, v) in entry_attrs.iteritems() + if v is not None and v != [] ) try: self.conn.add_s(dn, list(entry_attrs.iteritems())) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) # generating filters for find_entry # some examples: @@ -580,7 +926,9 @@ class ldap2(CrudBackend, Encoder): Keyword arguments: rules -- see ldap2.make_filter """ + assert isinstance(filters, (list, tuple)) + filters = [f for f in filters if f] if filters and rules == self.MATCH_NONE: # unary operator return '(%s%s)' % (self.MATCH_NONE, @@ -598,7 +946,6 @@ class ldap2(CrudBackend, Encoder): flt = '%s)' % flt return flt - @encode_args(1, 2) def make_filter_from_attr(self, attr, value, rules='|', exact=True, leading_wildcard=True, trailing_wildcard=True): """ @@ -620,7 +967,7 @@ class ldap2(CrudBackend, Encoder): trailing_wildcard=trailing_wildcard) for v in value ] return self.combine_filters(flts, rules) elif value is not None: - value = _ldap_filter.escape_filter_chars(value) + value = _ldap_filter.escape_filter_chars(value_to_utf8(value)) if not exact: template = '%s' if leading_wildcard: @@ -671,9 +1018,7 @@ class ldap2(CrudBackend, Encoder): ) return self.combine_filters(flts, rules) - @encode_args(1, 2, 3) - @decode_retval() - def find_entries(self, filter=None, attrs_list=None, base_dn='', + def find_entries(self, filter=None, attrs_list=None, base_dn=None, scope=_ldap.SCOPE_SUBTREE, time_limit=None, size_limit=None, normalize=True, search_refs=False): """ @@ -691,6 +1036,9 @@ class ldap2(CrudBackend, Encoder): normalize -- normalize the DN (default True) search_refs -- allow search references to be returned (default skips these entries) """ + if base_dn is None: + base_dn = DN() + assert isinstance(base_dn, DN) if normalize: base_dn = self.normalize_dn(base_dn) if not filter: @@ -731,7 +1079,7 @@ class ldap2(CrudBackend, Encoder): _ldap.SIZELIMIT_EXCEEDED), e: truncated = True except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) if not res and not truncated: raise errors.NotFound(reason='no such entry') @@ -756,7 +1104,8 @@ class ldap2(CrudBackend, Encoder): del r[1]['memberOf'] else: continue - (direct, indirect) = self.get_memberof(r[0], memberof, time_limit=time_limit, size_limit=size_limit, normalize=normalize) + (direct, indirect) = self.get_memberof(r[0], memberof, time_limit=time_limit, + size_limit=size_limit, normalize=normalize) if len(direct) > 0: r[1]['memberof'] = direct if len(indirect) > 0: @@ -764,8 +1113,7 @@ class ldap2(CrudBackend, Encoder): return (res, truncated) - def find_entry_by_attr(self, attr, value, object_class, attrs_list=None, - base_dn=''): + def find_entry_by_attr(self, attr, value, object_class, attrs_list=None, base_dn=None): """ Find entry (dn, entry_attrs) by attribute and object class. @@ -773,6 +1121,11 @@ class ldap2(CrudBackend, Encoder): attrs_list - list of attributes to return, all if None (default None) base_dn - dn of the entry at which to start the search (default '') """ + + if base_dn is None: + base_dn = DN() + assert isinstance(base_dn, DN) + search_kw = {attr: value, 'objectClass': object_class} filter = self.make_filter(search_kw, rules=self.MATCH_ALL) (entries, truncated) = self.find_entries(filter, attrs_list, base_dn) @@ -793,6 +1146,9 @@ class ldap2(CrudBackend, Encoder): Keyword arguments: attrs_list - list of attributes to return, all if None (default None) """ + + assert isinstance(dn, DN) + (entry, truncated) = self.find_entries( None, attrs_list, dn, self.SCOPE_BASE, time_limit=time_limit, size_limit=size_limit, normalize=normalize @@ -805,7 +1161,12 @@ class ldap2(CrudBackend, Encoder): config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]} def get_ipa_config(self, attrs_list=None): """Returns the IPA configuration entry (dn, entry_attrs).""" - cdn = "%s,%s" % (api.Object.config.get_dn(), api.env.basedn) + + odn = api.Object.config.get_dn() + assert isinstance(odn, DN) + assert isinstance(api.env.basedn, DN) + cdn = DN(odn, api.env.basedn) + try: config_entry = getattr(context, 'config_entry') return (cdn, copy.deepcopy(config_entry)) @@ -828,35 +1189,17 @@ class ldap2(CrudBackend, Encoder): setattr(context, 'config_entry', copy.deepcopy(config_entry)) return (cdn, config_entry) - def get_schema(self, deepcopy=False): - """Returns either a reference to current schema or its deep copy""" - global _schema - if not _schema: - _schema = get_schema(self.ldap_uri, self.conn) - if not _schema: - raise errors.DatabaseError(desc='Unable to retrieve LDAP schema', info='Unable to proceed with request') - # explicitly use setattr here so the schema can be set after - # the object is finalized. - object.__setattr__(self, 'schema', _schema) - - if (deepcopy): - return copy.deepcopy(self.schema) - else: - return self.schema - def has_upg(self): """Returns True/False whether User-Private Groups are enabled. This is determined based on whether the UPG Template exists. """ - upg_dn = str(DN('cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc', api.env.basedn)) + upg_dn = DN(('cn', 'UPG Definition'), ('cn', 'Definitions'), ('cn', 'Managed Entries'), + ('cn', 'etc'), api.env.basedn) try: - upg_entry = self.conn.search_s( - upg_dn, - _ldap.SCOPE_BASE, - attrlist=['*'] - )[0] + upg_entry = self.conn.search_s(upg_dn, _ldap.SCOPE_BASE, + attrlist=['*'])[0] disable_attr = '(objectclass=disable)' if 'originfilter' in upg_entry[1]: org_filter = upg_entry[1]['originfilter'] @@ -866,27 +1209,32 @@ class ldap2(CrudBackend, Encoder): except _ldap.NO_SUCH_OBJECT, e: return False - @encode_args(1, 2) def get_effective_rights(self, dn, entry_attrs): """Returns the rights the currently bound user has for the given DN. Returns 2 attributes, the attributeLevelRights for the given list of attributes and the entryLevelRights for the entry itself. """ + + assert isinstance(dn, DN) + principal = getattr(context, 'principal') (binddn, attrs) = self.find_entry_by_attr("krbprincipalname", principal, "krbPrincipalAux") - sctrl = [GetEffectiveRightsControl(True, "dn: " + binddn.encode('UTF-8'))] + assert isinstance(binddn, DN) + sctrl = [GetEffectiveRightsControl(True, "dn: " + str(binddn))] self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl) (dn, attrs) = self.get_entry(dn, entry_attrs) # remove the control so subsequent operations don't include GER self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, []) return (dn, attrs) - @encode_args(1, 2) def can_write(self, dn, attr): """Returns True/False if the currently bound user has write permissions on the attribute. This only operates on a single attribute at a time. """ + + assert isinstance(dn, DN) + (dn, attrs) = self.get_effective_rights(dn, [attr]) if 'attributelevelrights' in attrs: attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') @@ -896,11 +1244,12 @@ class ldap2(CrudBackend, Encoder): return False - @encode_args(1, 2) def can_read(self, dn, attr): """Returns True/False if the currently bound user has read permissions on the attribute. This only operates on a single attribute at a time. """ + assert isinstance(dn, DN) + (dn, attrs) = self.get_effective_rights(dn, [attr]) if 'attributelevelrights' in attrs: attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') @@ -919,11 +1268,13 @@ class ldap2(CrudBackend, Encoder): # v - View the entry # - @encode_args(1) def can_delete(self, dn): """Returns True/False if the currently bound user has delete permissions on the entry. """ + + assert isinstance(dn, DN) + (dn, attrs) = self.get_effective_rights(dn, ["*"]) if 'entrylevelrights' in attrs: entry_rights = attrs['entrylevelrights'][0].decode('UTF-8') @@ -932,11 +1283,11 @@ class ldap2(CrudBackend, Encoder): return False - @encode_args(1) def can_add(self, dn): """Returns True/False if the currently bound user has add permissions on the entry. """ + assert isinstance(dn, DN) (dn, attrs) = self.get_effective_rights(dn, ["*"]) if 'entrylevelrights' in attrs: entry_rights = attrs['entrylevelrights'][0].decode('UTF-8') @@ -945,7 +1296,6 @@ class ldap2(CrudBackend, Encoder): return False - @encode_args(1, 2) def update_entry_rdn(self, dn, new_rdn, del_old=True): """ Update entry's relative distinguished name. @@ -953,22 +1303,25 @@ class ldap2(CrudBackend, Encoder): Keyword arguments: del_old -- delete old RDN value (default True) """ + + assert isinstance(dn, DN) + assert isinstance(new_rdn, RDN) + dn = self.normalize_dn(dn) - if dn.startswith(new_rdn + ","): + if dn[0] == new_rdn: raise errors.EmptyModlist() try: self.conn.rename_s(dn, new_rdn, delold=int(del_old)) time.sleep(.3) # Give memberOf plugin a chance to work except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) def _generate_modlist(self, dn, entry_attrs, normalize): + assert isinstance(dn, DN) + # get original entry (dn, entry_attrs_old) = self.get_entry(dn, entry_attrs.keys(), normalize=normalize) - # get_entry returns a decoded entry, encode it back - # we could call search_s directly, but this saves a lot of code at - # the expense of a little bit of performace - entry_attrs_old = self.encode(entry_attrs_old) + # generate modlist # for multi value attributes: no MOD_REPLACE to handle simultaneous # updates better @@ -1009,13 +1362,14 @@ class ldap2(CrudBackend, Encoder): return modlist - @encode_args(1, 2) def update_entry(self, dn, entry_attrs, normalize=True): """ Update entry's attributes. An attribute value set to None deletes all current values. """ + + assert isinstance(dn, DN) if normalize: dn = self.normalize_dn(dn) @@ -1028,37 +1382,40 @@ class ldap2(CrudBackend, Encoder): try: self.conn.modify_s(dn, modlist) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) - @encode_args(1) def delete_entry(self, dn, normalize=True): """Delete entry.""" + + assert isinstance(dn, DN) if normalize: dn = self.normalize_dn(dn) + try: self.conn.delete_s(dn) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) - @encode_args(1, 2, 3) def modify_password(self, dn, new_pass, old_pass=''): """Set user password.""" + + assert isinstance(dn, DN) dn = self.normalize_dn(dn) # The python-ldap passwd command doesn't verify the old password # so we'll do a simple bind to validate it. if old_pass != '': try: - conn = _ldap.initialize(self.ldap_uri) + conn = IPASimpleLDAPObject(self.ldap_uri) conn.simple_bind_s(dn, old_pass) conn.unbind() except _ldap.LDAPError, e: - _handle_errors(e, **{}) + self.handle_errors(e) try: self.conn.passwd_s(dn, old_pass, new_pass) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) def add_entry_to_group(self, dn, group_dn, member_attr='member', allow_same=False): """ @@ -1068,12 +1425,18 @@ class ldap2(CrudBackend, Encoder): Adding a group as a member of itself is not allowed unless allow_same is True. """ + + assert isinstance(dn, DN) + assert isinstance(group_dn, DN) + + self.debug("add_entry_to_group: dn=%s group_dn=%s member_attr=%s", dn, group_dn, member_attr) # check if the entry exists (dn, entry_attrs) = self.get_entry(dn, ['objectclass']) # get group entry (group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr]) + self.debug("add_entry_to_group: group_entry_attrs=%s", group_entry_attrs) # check if we're not trying to add group into itself if dn == group_dn and not allow_same: raise errors.SameGroupError() @@ -1091,16 +1454,23 @@ class ldap2(CrudBackend, Encoder): def remove_entry_from_group(self, dn, group_dn, member_attr='member'): """Remove entry from group.""" + + assert isinstance(dn, DN) + assert isinstance(group_dn, DN) + + self.debug("remove_entry_from_group: dn=%s group_dn=%s member_attr=%s", dn, group_dn, member_attr) # get group entry (group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr]) + self.debug("remove_entry_from_group: group_entry_attrs=%s", group_entry_attrs) # remove dn from group entry's `member_attr` attribute - members = [DN(m) for m in group_entry_attrs.get(member_attr, [])] + members = group_entry_attrs.get(member_attr, []) + assert all([isinstance(x, DN) for x in members]) try: - members.remove(DN(dn)) + members.remove(dn) except ValueError: raise errors.NotGroupMember() - group_entry_attrs[member_attr] = [str(m) for m in members] + group_entry_attrs[member_attr] = members # update group entry self.update_entry(group_dn, group_entry_attrs) @@ -1118,10 +1488,14 @@ class ldap2(CrudBackend, Encoder): Returns a list of DNs. """ + + assert isinstance(group_dn, DN) + if membertype not in [MEMBERS_ALL, MEMBERS_DIRECT, MEMBERS_INDIRECT]: return None - search_group_dn = _ldap_filter.escape_filter_chars(group_dn) + self.debug("get_members: group_dn=%s members=%s membertype=%s", group_dn, members, membertype) + search_group_dn = _ldap_filter.escape_filter_chars(str(group_dn)) searchfilter = "(memberof=%s)" % search_group_dn attr_list.append("member") @@ -1130,18 +1504,23 @@ class ldap2(CrudBackend, Encoder): results = [] if membertype == MEMBERS_ALL or membertype == MEMBERS_INDIRECT: - checkmembers = copy.deepcopy(members) - for member in checkmembers: + user_container_dn = DN(api.env.container_user, api.env.basedn) # FIXME, initialize once + host_container_dn = DN(api.env.container_host, api.env.basedn) + checkmembers = set(DN(x) for x in members) + checked = set() + while checkmembers: + member_dn = checkmembers.pop() + checked.add(member_dn) + # No need to check entry types that are not nested for # additional members - dn = DN(member) - if dn.endswith(DN(api.env.container_user, api.env.basedn)) or \ - dn.endswith(DN(api.env.container_host, api.env.basedn)): - results.append([member, {}]) + if member_dn.endswith(user_container_dn) or \ + member_dn.endswith(host_container_dn): + results.append([member_dn, {}]) continue try: (result, truncated) = self.find_entries(searchfilter, - attr_list, member, time_limit=time_limit, + attr_list, member_dn, time_limit=time_limit, size_limit=size_limit, scope=_ldap.SCOPE_BASE, normalize=normalize) if truncated: @@ -1150,8 +1529,8 @@ class ldap2(CrudBackend, Encoder): for m in result[0][1].get('member', []): # This member may contain other members, add it to our # candidate list - if m not in checkmembers: - checkmembers.append(m) + if m not in checked: + checkmembers.add(m) except errors.NotFound: pass @@ -1164,21 +1543,18 @@ class ldap2(CrudBackend, Encoder): (dn, group) = self.get_entry(group_dn, ['dn', 'member'], size_limit=size_limit, time_limit=time_limit) - real_members = group.get('member') - if isinstance(real_members, basestring): - real_members = [real_members] - if real_members is None: - real_members = [] + real_members = group.get('member', []) entries = [] for e in results: - if unicode(e[0]) not in real_members and unicode(e[0]) not in entries: + if e[0] not in real_members and e[0] not in entries: if membertype == MEMBERS_INDIRECT: entries.append(e[0]) else: if membertype == MEMBERS_DIRECT: entries.append(e[0]) + self.debug("get_members: result=%s", entries) return entries def get_memberof(self, entry_dn, memberof, time_limit=None, size_limit=None, normalize=True): @@ -1192,12 +1568,15 @@ class ldap2(CrudBackend, Encoder): Returns two memberof lists: (direct, indirect) """ + assert isinstance(entry_dn, DN) + + self.debug("get_memberof: entry_dn=%s memberof=%s", entry_dn, memberof) if not type(memberof) in (list, tuple): return ([], []) if len(memberof) == 0: return ([], []) - search_entry_dn = _ldap_filter.escape_filter_chars(entry_dn) + search_entry_dn = _ldap_filter.escape_filter_chars(str(entry_dn)) attr_list = ["dn", "memberof"] searchfilter = "(|(member=%s)(memberhost=%s)(memberuser=%s))" % ( search_entry_dn, search_entry_dn, search_entry_dn) @@ -1207,6 +1586,7 @@ class ldap2(CrudBackend, Encoder): results = [] for group in memberof: + assert isinstance(group, DN) try: (result, truncated) = self.find_entries(searchfilter, attr_list, group, time_limit=time_limit,size_limit=size_limit, @@ -1219,20 +1599,24 @@ class ldap2(CrudBackend, Encoder): # If there is an exception here, it is likely due to a failure in # referential integrity. All members should have corresponding # memberOf entries. - indirect = [ m.lower() for m in memberof ] + indirect = list(memberof) for r in results: direct.append(r[0]) try: - indirect.remove(r[0].lower()) + indirect.remove(r[0]) except ValueError, e: - root_logger.info('Failed to remove indirect entry %s from %s' % r[0], entry_dn) + self.info('Failed to remove indirect entry %s from %s' % r[0], entry_dn) raise e + self.debug("get_memberof: result direct=%s indirect=%s", direct, indirect) return (direct, indirect) def set_entry_active(self, dn, active): """Mark entry active/inactive.""" + + assert isinstance(dn, DN) assert isinstance(active, bool) + # get the entry in question (dn, entry_attrs) = self.get_entry(dn, ['nsaccountlock']) @@ -1255,15 +1639,20 @@ class ldap2(CrudBackend, Encoder): def activate_entry(self, dn): """Mark entry active.""" + + assert isinstance(dn, DN) self.set_entry_active(dn, True) def deactivate_entry(self, dn): """Mark entry inactive.""" + + assert isinstance(dn, DN) self.set_entry_active(dn, False) def remove_principal_key(self, dn): """Remove a kerberos principal key.""" + assert isinstance(dn, DN) dn = self.normalize_dn(dn) # We need to do this directly using the LDAP library because we @@ -1275,11 +1664,14 @@ class ldap2(CrudBackend, Encoder): try: self.conn.modify_s(dn, mod) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) # CrudBackend methods def _get_normalized_entry_for_crud(self, dn, attrs_list=None): + + assert isinstance(dn, DN) + (dn, entry_attrs) = self.get_entry(dn, attrs_list) entry_attrs['dn'] = dn return entry_attrs @@ -1292,6 +1684,7 @@ class ldap2(CrudBackend, Encoder): """ assert 'dn' in kw dn = kw['dn'] + assert isinstance(dn, DN) del kw['dn'] self.add_entry(dn, kw) return self._get_normalized_entry_for_crud(dn) @@ -1337,7 +1730,8 @@ class ldap2(CrudBackend, Encoder): # get keyword arguments filter = kw.pop('filter', None) attrs_list = kw.pop('attrs_list', None) - base_dn = kw.pop('base_dn', '') + base_dn = kw.pop('base_dn', DN()) + assert isinstance(base_dn, DN) scope = kw.pop('scope', self.SCOPE_SUBTREE) # generate filter @@ -1363,4 +1757,3 @@ class ldap2(CrudBackend, Encoder): return (len(output), output) api.register(ldap2) - diff --git a/ipaserver/plugins/selfsign.py b/ipaserver/plugins/selfsign.py index bbf8fa78a..09ed04f49 100644 --- a/ipaserver/plugins/selfsign.py +++ b/ipaserver/plugins/selfsign.py @@ -39,6 +39,7 @@ from ipalib import Backend from ipalib import errors from ipalib import x509 from ipalib import pkcs10 +from ipapython.dn import DN, EditableDN, RDN from ipapython.certdb import get_ca_nickname import subprocess import os @@ -86,16 +87,14 @@ class ra(rabase.rabase): """ try: config = api.Command['config_show']()['result'] - subject_base = config.get('ipacertificatesubjectbase')[0] + subject_base = EditableDN(config.get('ipacertificatesubjectbase')[0]) hostname = get_csr_hostname(csr) - base = re.split(',\s*(?=\w+=)', subject_base) - base.insert(0,'CN=%s' % hostname) - subject_base = ",".join(base) + subject_base.insert(0, RDN(('CN', hostname))) request = pkcs10.load_certificate_request(csr) # python-nss normalizes the request subject - request_subject = str(pkcs10.get_subject(request)) + request_subject = DN(pkcs10.get_subject(request)) - if str(subject_base).lower() != request_subject.lower(): + if subject_base != request_subject: raise errors.CertificateOperationError(error=_('Request subject "%(request_subject)s" does not match the form "%(subject_base)s"') % \ {'request_subject' : request_subject, 'subject_base' : subject_base}) except errors.CertificateOperationError, e: diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index c770290f1..f7b71b32f 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -32,7 +32,7 @@ from ipalib.errors import PublicError, InternalError, CommandError, JSONError, C from ipalib.request import context, Connection, destroy_context from ipalib.rpc import xml_dumps, xml_loads from ipalib.util import parse_time_duration -from ipalib.dn import DN +from ipapython.dn import DN from ipaserver.plugins.ldap2 import ldap2 from ipapython.compat import json from ipalib.session import session_mgr, AuthManager, get_ipa_ccache_name, load_ccache_data, bind_ipa_ccache, release_ipa_ccache, fmt_time, default_max_session_duration @@ -418,29 +418,17 @@ def json_encode_binary(val): if isinstance(val, dict): new_dict = {} for k,v in val.items(): - if isinstance(v, str): - new_dict[k] = {'__base64__' : base64.b64encode(v)} - else: - new_dict[k] = json_encode_binary(v) - del val + new_dict[k] = json_encode_binary(v) return new_dict elif isinstance(val, (list, tuple)): - new_list = [] - n = len(val) - i = 0 - while i < n: - v = val[i] - if isinstance(v, str): - new_list.append({'__base64__' : base64.b64encode(v)}) - else: - new_list.append(json_encode_binary(v)) - i += 1 - del val + new_list = [json_encode_binary(v) for v in val] return new_list elif isinstance(val, str): return {'__base64__' : base64.b64encode(val)} elif isinstance(val, Decimal): return {'__base64__' : base64.b64encode(str(val))} + elif isinstance(val, DN): + return str(val) else: return val @@ -474,7 +462,6 @@ def json_decode_binary(val): new_dict[k] = base64.b64decode(v['__base64__']) else: new_dict[k] = json_decode_binary(v) - del val return new_dict elif isinstance(val, list): new_list = [] @@ -488,7 +475,6 @@ def json_decode_binary(val): else: new_list.append(json_decode_binary(v)) i += 1 - del val return new_list else: if isinstance(val, basestring): @@ -963,9 +949,9 @@ class login_password(Backend, KerberosSession, HTTP_Status): # Ok, now why is this bad. Is the password simply bad or is the # password expired? try: - dn = str(DN(('uid', user), - self.api.env.container_user, - self.api.env.basedn)) + dn = DN(('uid', user), + self.api.env.container_user, + self.api.env.basedn) conn = ldap2(shared_instance=False, ldap_uri=self.api.env.ldap_uri) conn.connect(bind_dn=dn, bind_pw=password) @@ -1059,8 +1045,8 @@ class change_password(Backend, HTTP_Status): result = 'error' policy_error = None - bind_dn = str(DN((self.api.Object.user.primary_key.name, data['user']), - self.api.env.container_user, self.api.env.basedn)) + bind_dn = DN((self.api.Object.user.primary_key.name, data['user']), + self.api.env.container_user, self.api.env.basedn) try: conn = ldap2(shared_instance=False, diff --git a/make-lint b/make-lint index 05a1bb14f..29ce758ed 100755 --- a/make-lint +++ b/make-lint @@ -65,6 +65,10 @@ class IPATypeChecker(TypeChecker): 'ipalib.parameters.Enum': ['values'], 'ipalib.parameters.File': ['stdin_if_missing'], 'urlparse.SplitResult': ['netloc'], + 'ipaserver.install.ldapupdate.LDAPUpdate' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], + 'ipaserver.plugins.ldap2.SchemaCache' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], + 'ipaserver.plugins.ldap2.IPASimpleLDAPObject' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], + 'ipaserver.plugins.ldap2.ldap2' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], 'ipaserver.rpcserver.KerberosSession' : ['api', 'log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], 'ipaserver.rpcserver.HTTP_Status' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], 'ipalib.krb_utils.KRB5_CCache' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'], diff --git a/make-testcert b/make-testcert index f5a2fa775..a5814e1de 100755 --- a/make-testcert +++ b/make-testcert @@ -31,6 +31,7 @@ import nss.nss as nss from ipalib import api, x509, backend, errors from ipaserver.plugins import rabase from ipapython import ipautil +from ipapython.dn import DN CERTPATH = 'tests/test_xmlrpc/service.crt' @@ -101,9 +102,9 @@ def makecert(reqdir): subject_base = res['result']['ipacertificatesubjectbase'][0] cert = None - subject = 'CN=%s,%s' % (api.env.host, subject_base) + subject = DN(('CN', api.env.host), subject_base) princ = 'unittest/%s@%s' % (api.env.host, api.env.realm) - csr = unicode(generateCSR(reqdir, pwname, subject)) + csr = unicode(generateCSR(reqdir, pwname, str(subject))) try: res = api.Backend.client.run('cert_request', csr, principal=princ, diff --git a/tests/test_cmdline/test_ipagetkeytab.py b/tests/test_cmdline/test_ipagetkeytab.py index 0437c80bb..b01fdee9f 100644 --- a/tests/test_cmdline/test_ipagetkeytab.py +++ b/tests/test_cmdline/test_ipagetkeytab.py @@ -31,7 +31,7 @@ import nose import tempfile import krbV from ipaserver.plugins.ldap2 import ldap2 -from ipalib.dn import * +from ipapython.dn import DN def use_keytab(principal, keytab): try: diff --git a/tests/test_install/0_reset.update b/tests/test_install/0_reset.update index ba2af5e06..bd6ee636d 100644 --- a/tests/test_install/0_reset.update +++ b/tests/test_install/0_reset.update @@ -1,5 +1,5 @@ dn: uid=tuser, cn=test, cn=accounts, $SUFFIX -deleteentry: reset: nada +deleteentry: dn: cn=test, cn=accounts, $SUFFIX deleteentry: reset: nada diff --git a/tests/test_install/test_updates.py b/tests/test_install/test_updates.py index 8b9ac28eb..711af3db4 100644 --- a/tests/test_install/test_updates.py +++ b/tests/test_install/test_updates.py @@ -20,19 +20,18 @@ Test the `ipaserver/install/ldapupdate.py` module. """ +import unittest import os import sys import ldap import nose -from nose.tools import raises -from tests.util import PluginTester -from tests.data import unicode_str from ipalib import api from ipalib import errors from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR from ipaserver.install import installutils from ipaserver import ipaldap from ipapython import ipautil +from ipapython.dn import DN """ The updater works through files only so this is just a thin-wrapper controlling @@ -46,7 +45,7 @@ have occurred as expected. The DM password needs to be set in ~/.ipa/.dmpw """ -class test_update(object): +class test_update(unittest.TestCase): """ Test the LDAP updater. """ @@ -70,13 +69,16 @@ class test_update(object): else: raise nose.SkipTest("Unable to find test update files") + self.container_dn = DN(self.updater._template_str('cn=test, cn=accounts, $SUFFIX')) + self.user_dn = DN(self.updater._template_str('uid=tuser, cn=test, cn=accounts, $SUFFIX')) + def tearDown(self): if self.ld: self.ld.unbind() def test_0_reset(self): """ - Reset the updater test data to a known initial state + Reset the updater test data to a known initial state (test_0_reset) """ try: modified = self.updater.update([self.testdir + "0_reset.update"]) @@ -84,96 +86,121 @@ class test_update(object): # Just means the entry doesn't exist yet modified = True - assert(modified == True) + self.assertTrue(modified) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.container_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) def test_1_add(self): """ - Test the updater with an add directive + Test the updater with an add directive (test_1_add) """ modified = self.updater.update([self.testdir + "1_add.update"]) - assert(modified == True) + self.assertTrue(modified) + + entries = self.ld.getList(self.container_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + + objectclasses = entry.getValues('objectclass') + for item in ('top', 'nsContainer'): + self.assertTrue(item in objectclasses) + + self.assertEqual(entry.getValue('cn'), 'test') + + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + + objectclasses = entry.getValues('objectclass') + for item in ('top', 'person', 'posixaccount', 'krbprincipalaux', 'inetuser'): + self.assertTrue(item in objectclasses) + + self.assertEqual(entry.getValue('loginshell'), '/bin/bash') + self.assertEqual(entry.getValue('sn'), 'User') + self.assertEqual(entry.getValue('uid'), 'tuser') + self.assertEqual(entry.getValue('cn'), 'Test User') + def test_2_update(self): """ - Test the updater when adding an attribute to an existing entry + Test the updater when adding an attribute to an existing entry (test_2_update) """ modified = self.updater.update([self.testdir + "2_update.update"]) - assert(modified == True) + self.assertTrue(modified) - # The update passed, lets look at the record and see if it is - # really updated - dn = self.updater._template_str('uid=tuser, cn=test, cn=accounts, $SUFFIX') - entry = self.ld.getList(dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) - assert (len(entry) == 1) - assert(entry[0].gecos == 'Test User') + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + self.assertEqual(entry.getValue('gecos'), 'Test User') def test_3_update(self): """ - Test the updater forcing an attribute to a given value + Test the updater forcing an attribute to a given value (test_3_update) """ modified = self.updater.update([self.testdir + "3_update.update"]) - assert(modified == True) + self.assertTrue(modified) - # The update passed, lets look at the record and see if it is - # really updated - dn = self.updater._template_str('uid=tuser, cn=test, cn=accounts, $SUFFIX') - entry = self.ld.getList(dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) - assert (len(entry) == 1) - assert(entry[0].gecos == 'Test User New') + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + self.assertEqual(entry.getValue('gecos'), 'Test User New') def test_4_update(self): """ - Test the updater adding a new value to a single-valued attribute + Test the updater adding a new value to a single-valued attribute (test_4_update) """ modified = self.updater.update([self.testdir + "4_update.update"]) - assert(modified == True) + self.assertTrue(modified) + + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + self.assertEqual(entry.getValue('gecos'), 'Test User New2') def test_5_update(self): """ - Test the updater adding a new value to a multi-valued attribute + Test the updater adding a new value to a multi-valued attribute (test_5_update) """ modified = self.updater.update([self.testdir + "5_update.update"]) - assert(modified == True) + self.assertTrue(modified) - # The update passed, lets look at the record and see if it is - # really updated - dn = self.updater._template_str('uid=tuser, cn=test, cn=accounts, $SUFFIX') - entry = self.ld.getList(dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) - assert (len(entry) == 1) - assert(entry[0].getValues('cn') == ['Test User', 'Test User New']) + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + self.assertEqual(sorted(entry.getValues('cn')), sorted(['Test User', 'Test User New'])) def test_6_update(self): """ - Test the updater removing a value from a multi-valued attribute + Test the updater removing a value from a multi-valued attribute (test_6_update) """ modified = self.updater.update([self.testdir + "6_update.update"]) - assert(modified == True) + self.assertTrue(modified) - # The update passed, lets look at the record and see if it is - # really updated - dn = self.updater._template_str('uid=tuser, cn=test, cn=accounts, $SUFFIX') - entry = self.ld.getList(dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) - assert (len(entry) == 1) - assert(entry[0].cn == 'Test User') + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + self.assertEqual(sorted(entry.getValues('cn')), sorted(['Test User'])) def test_6_update_1(self): """ - Test the updater removing a non-existent value from a multi-valued attribute + Test the updater removing a non-existent value from a multi-valued attribute (test_6_update_1) """ modified = self.updater.update([self.testdir + "6_update.update"]) - assert(modified == False) + self.assertFalse(modified) - # The update passed, lets look at the record and see if it is - # really updated - dn = self.updater._template_str('uid=tuser, cn=test, cn=accounts, $SUFFIX') - entry = self.ld.getList(dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) - assert (len(entry) == 1) - assert(entry[0].cn == 'Test User') + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + self.assertEqual(sorted(entry.getValues('cn')), sorted(['Test User'])) def test_7_cleanup(self): """ - Reset the test data to a known initial state + Reset the test data to a known initial state (test_7_cleanup) """ try: modified = self.updater.update([self.testdir + "0_reset.update"]) @@ -181,18 +208,113 @@ class test_update(object): # Just means the entry doesn't exist yet modified = True - assert(modified == True) + self.assertTrue(modified) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.container_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) - @raises(BadSyntax) def test_8_badsyntax(self): """ - Test the updater with an unknown keyword + Test the updater with an unknown keyword (test_8_badsyntax) """ - modified = self.updater.update([self.testdir + "8_badsyntax.update"]) + with self.assertRaises(BadSyntax): + modified = self.updater.update([self.testdir + "8_badsyntax.update"]) - @raises(BadSyntax) def test_9_badsyntax(self): """ - Test the updater with an incomplete line + Test the updater with an incomplete line (test_9_badsyntax) + """ + with self.assertRaises(BadSyntax): + modified = self.updater.update([self.testdir + "9_badsyntax.update"]) + + def test_from_dict(self): + """ + Test updating from a dict. + + This replicates what was done in test 1. """ - modified = self.updater.update([self.testdir + "9_badsyntax.update"]) + + # First make sure we're clean + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.container_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + + + update = { + self.container_dn: + {'dn': self.container_dn, + 'updates': ['add:objectClass: top', + 'add:objectClass: nsContainer', + 'add:cn: test' + ], + }, + self.user_dn: + {'dn': self.user_dn, + 'updates': ['add:objectclass: top', + 'add:objectclass: person', + 'add:objectclass: posixaccount', + 'add:objectclass: krbprincipalaux', + 'add:objectclass: inetuser', + 'add:homedirectory: /home/tuser', + 'add:loginshell: /bin/bash', + 'add:sn: User', + 'add:uid: tuser', + 'add:uidnumber: 999', + 'add:gidnumber: 999', + 'add:cn: Test User', + ], + }, + } + + modified = self.updater.update_from_dict(update) + self.assertTrue(modified) + + entries = self.ld.getList(self.container_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + + objectclasses = entry.getValues('objectclass') + for item in ('top', 'nsContainer'): + self.assertTrue(item in objectclasses) + + self.assertEqual(entry.getValue('cn'), 'test') + + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + self.assertEqual(len(entries), 1) + entry = entries[0] + + objectclasses = entry.getValues('objectclass') + for item in ('top', 'person', 'posixaccount', 'krbprincipalaux', 'inetuser'): + self.assertTrue(item in objectclasses) + + self.assertEqual(entry.getValue('loginshell'), '/bin/bash') + self.assertEqual(entry.getValue('sn'), 'User') + self.assertEqual(entry.getValue('uid'), 'tuser') + self.assertEqual(entry.getValue('cn'), 'Test User') + + # Now delete + + update = { + self.container_dn: + {'dn': self.container_dn, + 'deleteentry': None, + }, + self.user_dn: + {'dn': self.user_dn, + 'deleteentry': 'deleteentry: reset: nada', + }, + } + + modified = self.updater.update_from_dict(update) + self.assertTrue(modified) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.container_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) + + with self.assertRaises(errors.NotFound): + entries = self.ld.getList(self.user_dn, ldap.SCOPE_BASE, 'objectclass=*', ['*']) diff --git a/tests/test_ipalib/test_dn.py b/tests/test_ipalib/test_dn.py deleted file mode 100644 index 04e442f3f..000000000 --- a/tests/test_ipalib/test_dn.py +++ /dev/null @@ -1,1085 +0,0 @@ -#!/usr/bin/python - -import unittest -from ipalib.dn import AVA, RDN, DN - -def default_rdn_attr_arg(i): - return 'a%d' % i - -def default_rdn_value_arg(i): - return str(i) - -def alt_rdn_attr_arg(i): - return 'b%d' % i - -def alt_rdn_value_arg(i): - return str(i*10) - -def make_rdn_args(low, high, kind, attr=None, value=None): - result=[] - for i in range(low, high): - if attr is None: - new_attr = default_rdn_attr_arg(i) - elif callable(attr): - new_attr = attr(i) - else: - new_attr = attr - - if value is None: - new_value = default_rdn_value_arg(i) - elif callable(value): - new_value = value(i) - else: - new_value = value - - if kind == 'tuple': - result.append((new_attr, new_value)) - elif kind == 'list': - result.append([new_attr, new_value]) - elif kind == 'RDN': - result.append(RDN((new_attr, new_value))) - else: - raise ValueError("Unknown kind = %s" % kind) - - return result - -class TestAVA(unittest.TestCase): - def setUp(self): - self.attr1 = 'cn' - self.value1 = 'Bob' - self.str_ava1 = '%s=%s' % (self.attr1, self.value1) - self.ava1 = AVA(self.attr1, self.value1) - - def test_create(self): - # Create with attr,value pair - ava1 = AVA(self.attr1, self.value1) - self.assertEqual(ava1, self.ava1) - - # Create with "attr=value" string - ava1 = AVA(self.str_ava1) - self.assertEqual(ava1, self.ava1) - - # Create with tuple (attr, value) - ava1 = AVA((self.attr1, self.value1)) - self.assertEqual(ava1, self.ava1) - - # Create with list [attr, value] - ava1 = AVA([self.attr1, self.value1]) - self.assertEqual(ava1, self.ava1) - - # Create with no args should fail - with self.assertRaises(TypeError): - AVA() - - # Create with more than 2 args should fail - with self.assertRaises(TypeError): - AVA(self.attr1, self.value1, self.attr1) - - # Create with 1 arg which is not string should fail - with self.assertRaises(TypeError): - AVA(1) - - # Create with malformed AVA string should fail - with self.assertRaises(ValueError): - AVA("cn") - - # Create with non-string parameters, should fail - with self.assertRaises(TypeError): - AVA(1, self.value1) - - with self.assertRaises(TypeError): - AVA(self.attr1, 1) - - with self.assertRaises(TypeError): - AVA((1, self.value1)) - - with self.assertRaises(TypeError): - AVA((self.attr1, 1)) - - def test_encoding(self): - # Create with attr,value pair - ava1 = AVA(self.attr1, self.value1) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - ava1 = AVA(unicode(self.attr1), self.value1) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - ava1 = AVA(self.attr1, unicode(self.value1)) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - # Create with "attr=value" string - ava1 = AVA(self.str_ava1) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - ava1 = AVA(unicode(self.ava1)) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - # Create with tuple (attr, value) - ava1 = AVA((unicode(self.attr1), self.value1)) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - ava1 = AVA((self.attr1, unicode(self.value1))) - self.assertEqual(ava1, self.ava1) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - - def test_indexing(self): - self.assertEqual(self.ava1[self.attr1], self.value1) - - with self.assertRaises(KeyError): - self.ava1['foo'] - - with self.assertRaises(TypeError): - self.ava1[0] - - def test_properties(self): - self.assertEqual(self.ava1.attr, self.attr1) - self.assertEqual(self.ava1.value, self.value1) - - def test_str(self): - self.assertEqual(str(self.ava1), self.str_ava1) - self.assertIsInstance(str(self.ava1), str) - - def test_cmp(self): - # Equality - ava1 = AVA(self.attr1, self.value1) - - self.assertTrue(ava1 == self.ava1) - self.assertFalse(ava1 != self.ava1) - - result = cmp(ava1, self.ava1) - self.assertEqual(result, 0) - - # Upper case attr should still be equal - ava1 = AVA(self.attr1.upper(), self.value1) - - self.assertFalse(ava1.attr == self.attr1) - self.assertTrue(ava1.value == self.value1) - self.assertTrue(ava1 == self.ava1) - self.assertFalse(ava1 != self.ava1) - - result = cmp(ava1, self.ava1) - self.assertEqual(result, 0) - - # Upper case value should still be equal - ava1 = AVA(self.attr1, self.value1.upper()) - - self.assertTrue(ava1.attr == self.attr1) - self.assertFalse(ava1.value == self.value1) - self.assertTrue(ava1 == self.ava1) - self.assertFalse(ava1 != self.ava1) - - result = cmp(ava1, self.ava1) - self.assertEqual(result, 0) - - # Make ava1's attr greater - ava1.attr = self.attr1 + "1" - - self.assertFalse(ava1 == self.ava1) - self.assertTrue(ava1 != self.ava1) - - result = cmp(ava1, self.ava1) - self.assertEqual(result, 1) - - result = cmp(self.ava1, ava1) - self.assertEqual(result, -1) - - # Reset ava1's attr, should be equal again - ava1.attr = self.attr1 - - result = cmp(ava1, self.ava1) - self.assertEqual(result, 0) - - # Make ava1's value greater - # attr will be equal, this tests secondary comparision component - ava1.value = self.value1 + "1" - - result = cmp(ava1, self.ava1) - self.assertEqual(result, 1) - - result = cmp(self.ava1, ava1) - self.assertEqual(result, -1) - -class TestRDN(unittest.TestCase): - def setUp(self): - # ava1 must sort before ava2 - self.attr1 = 'cn' - self.value1 = 'Bob' - self.str_ava1 = '%s=%s' % (self.attr1, self.value1) - self.ava1 = AVA(self.attr1, self.value1) - - self.str_rdn1 = '%s=%s' % (self.attr1, self.value1) - self.rdn1 = RDN((self.attr1, self.value1)) - - self.attr2 = 'ou' - self.value2 = 'people' - self.str_ava2 = '%s=%s' % (self.attr2, self.value2) - self.ava2 = AVA(self.attr2, self.value2) - - self.str_rdn2 = '%s=%s' % (self.attr2, self.value2) - self.rdn2 = RDN((self.attr2, self.value2)) - - self.str_ava3 = '%s=%s+%s=%s' % (self.attr1, self.value1, self.attr2, self.value2) - - self.str_rdn3 = '%s=%s+%s=%s' % (self.attr1, self.value1, self.attr2, self.value2) - self.rdn3 = RDN(self.ava1, self.ava2) - - def test_create(self): - # Create with single attr,value pair - rdn1 = RDN((self.attr1, self.value1)) - self.assertEqual(len(rdn1), 1) - self.assertEqual(rdn1, self.rdn1) - self.assertIsInstance(rdn1[0], AVA) - self.assertEqual(rdn1[0], self.ava1) - - # Create with multiple attr,value pairs - rdn3 = RDN((self.attr1, self.value1), (self.attr2, self.value2)) - self.assertEqual(len(rdn3), 2) - self.assertEqual(rdn3, self.rdn3) - self.assertIsInstance(rdn3[0], AVA) - self.assertEqual(rdn3[0], self.ava1) - self.assertIsInstance(rdn3[1], AVA) - self.assertEqual(rdn3[1], self.ava2) - - # Create with multiple attr,value pairs passed as lists - rdn3 = RDN([self.attr1, self.value1], [self.attr2, self.value2]) - self.assertEqual(len(rdn3), 2) - self.assertEqual(rdn3, self.rdn3) - self.assertIsInstance(rdn3[0], AVA) - self.assertEqual(rdn3[0], self.ava1) - self.assertIsInstance(rdn3[1], AVA) - self.assertEqual(rdn3[1], self.ava2) - - # Create with multiple attr,value pairs but reverse - # constructor parameter ordering. RDN canonical ordering - # should remain the same - rdn3 = RDN((self.attr2, self.value2), (self.attr1, self.value1)) - self.assertEqual(len(rdn3), 2) - self.assertEqual(rdn3, self.rdn3) - self.assertIsInstance(rdn3[0], AVA) - self.assertEqual(rdn3[0], self.ava1) - self.assertIsInstance(rdn3[1], AVA) - self.assertEqual(rdn3[1], self.ava2) - - # Create with single AVA object - rdn1 = RDN(self.ava1) - self.assertEqual(len(rdn1), 1) - self.assertEqual(rdn1, self.rdn1) - self.assertIsInstance(rdn1[0], AVA) - self.assertEqual(rdn1[0], self.ava1) - - # Create with multiple AVA objects - rdn3 = RDN(self.ava1, self.ava2) - self.assertEqual(len(rdn3), 2) - self.assertEqual(rdn3, self.rdn3) - self.assertIsInstance(rdn3[0], AVA) - self.assertEqual(rdn3[0], self.ava1) - self.assertIsInstance(rdn3[1], AVA) - self.assertEqual(rdn3[1], self.ava2) - - - # Create with multiple AVA objects but reverse constructor - # parameter ordering. RDN canonical ordering should remain - # the same - rdn3 = RDN(self.ava2, self.ava1) - self.assertEqual(len(rdn3), 2) - self.assertEqual(rdn3, self.rdn3) - self.assertIsInstance(rdn3[0], AVA) - self.assertEqual(rdn3[0], self.ava1) - self.assertIsInstance(rdn3[1], AVA) - self.assertEqual(rdn3[1], self.ava2) - - # Create with single string with 1 AVA - rdn1 = RDN(self.str_rdn1) - self.assertEqual(len(rdn1), 1) - self.assertEqual(rdn1, self.rdn1) - self.assertIsInstance(rdn1[0], AVA) - self.assertEqual(rdn1[0], self.ava1) - - # Create with single string with 2 AVA's - rdn3 = RDN(self.str_rdn3) - self.assertEqual(len(rdn3), 2) - self.assertEqual(rdn3, self.rdn3) - self.assertIsInstance(rdn3[0], AVA) - self.assertEqual(rdn3[0], self.ava1) - self.assertIsInstance(rdn3[1], AVA) - self.assertEqual(rdn3[1], self.ava2) - - def test_properties(self): - self.assertEqual(self.rdn1.attr, self.attr1) - self.assertIsInstance(self.rdn1.attr, unicode) - - self.assertEqual(self.rdn1.value, self.value1) - self.assertIsInstance(self.rdn1.value, unicode) - - self.assertEqual(self.rdn2.attr, self.attr2) - self.assertIsInstance(self.rdn2.attr, unicode) - - self.assertEqual(self.rdn2.value, self.value2) - self.assertIsInstance(self.rdn2.value, unicode) - - self.assertEqual(self.rdn3.attr, self.attr1) - self.assertIsInstance(self.rdn3.attr, unicode) - - self.assertEqual(self.rdn3.value, self.value1) - self.assertIsInstance(self.rdn3.value, unicode) - - def test_str(self): - self.assertEqual(str(self.rdn1), self.str_rdn1) - self.assertIsInstance(str(self.rdn1), str) - - self.assertEqual(str(self.rdn2), self.str_rdn2) - self.assertIsInstance(str(self.rdn2), str) - - self.assertEqual(str(self.rdn3), self.str_rdn3) - self.assertIsInstance(str(self.rdn3), str) - - def test_cmp(self): - # Equality - rdn1 = RDN((self.attr1, self.value1)) - - self.assertTrue(rdn1 == self.rdn1) - self.assertFalse(rdn1 != self.rdn1) - - result = cmp(rdn1, self.rdn1) - self.assertEqual(result, 0) - - # Make rdn1's attr greater - rdn1.attr = self.attr1 + "1" - - self.assertFalse(rdn1 == self.rdn1) - self.assertTrue(rdn1 != self.rdn1) - - result = cmp(rdn1, self.rdn1) - self.assertEqual(result, 1) - - result = cmp(self.rdn1, rdn1) - self.assertEqual(result, -1) - - # Reset rdn1's attr, should be equal again - rdn1.attr = self.attr1 - - result = cmp(rdn1, self.rdn1) - self.assertEqual(result, 0) - - # Make rdn1's value greater - # attr will be equal, this tests secondary comparision component - rdn1.value = self.value1 + "1" - - result = cmp(rdn1, self.rdn1) - self.assertEqual(result, 1) - - result = cmp(self.rdn1, rdn1) - self.assertEqual(result, -1) - - # Make sure rdn's with more ava's are greater - result = cmp(self.rdn1, self.rdn3) - self.assertEqual(result, -1) - result = cmp(self.rdn3, self.rdn1) - self.assertEqual(result, 1) - - def test_indexing(self): - self.assertEqual(self.rdn1[0], self.ava1) - self.assertEqual(self.rdn1[self.ava1.attr], self.ava1.value) - with self.assertRaises(KeyError): - self.rdn1['foo'] - - self.assertEqual(self.rdn2[0], self.ava2) - self.assertEqual(self.rdn2[self.ava2.attr], self.ava2.value) - with self.assertRaises(KeyError): - self.rdn2['foo'] - - self.assertEqual(self.rdn3[0], self.ava1) - self.assertEqual(self.rdn3[self.ava1.attr], self.ava1.value) - self.assertEqual(self.rdn3[1], self.ava2) - self.assertEqual(self.rdn3[self.ava2.attr], self.ava2.value) - with self.assertRaises(KeyError): - self.rdn3['foo'] - - self.assertEqual(self.rdn1.attr, self.attr1) - self.assertEqual(self.rdn1.value, self.value1) - - with self.assertRaises(TypeError): - self.rdn3[1.0] - - # Slices - self.assertEqual(self.rdn3[0:1], [self.ava1]) - self.assertEqual(self.rdn3[:], [self.ava1, self.ava2]) - - def test_assignments(self): - rdn = RDN((self.attr1, self.value1)) - rdn[0] = self.ava2 - self.assertEqual(rdn, self.rdn2) - - rdn = RDN((self.attr1, self.value1)) - rdn[0] = (self.attr2, self.value2) - self.assertEqual(rdn, self.rdn2) - - rdn = RDN((self.attr1, self.value1)) - rdn[self.attr1] = self.str_ava2 - self.assertEqual(rdn[0], self.ava2) - - # Can't assign multiples to single entry - rdn = RDN((self.attr1, self.value1)) - with self.assertRaises(TypeError): - rdn[self.attr1] = self.str_ava3 - - rdn = RDN((self.attr1, self.value1)) - with self.assertRaises(TypeError): - rdn[self.attr1] = (self.attr1, self.value1, self.attr2, self.value2) - - rdn = RDN((self.attr1, self.value1)) - with self.assertRaises(TypeError): - rdn[self.attr1] = [(self.attr1, self.value1), (self.attr2, self.value2)] - - # Slices - rdn = RDN((self.attr1, self.value1)) - self.assertEqual(rdn, self.rdn1) - rdn[0:1] = [self.ava2] - self.assertEqual(rdn, self.rdn2) - - rdn = RDN((self.attr1, self.value1)) - self.assertEqual(rdn, self.rdn1) - rdn[:] = [(self.attr2, self.value2)] - self.assertEqual(rdn, self.rdn2) - - rdn = RDN((self.attr1, self.value1)) - self.assertEqual(rdn, self.rdn1) - rdn[:] = [(self.attr1, self.value1),(self.attr2, self.value2)] - self.assertEqual(rdn, self.rdn3) - - rdn = RDN((self.attr1, self.value1)) - self.assertEqual(rdn, self.rdn1) - rdn[0:1] = [(self.attr1, self.value1), (self.attr2, self.value2)] - self.assertEqual(rdn, self.rdn3) - - - def test_iter(self): - self.assertEqual(len(self.rdn1), 1) - self.assertEqual(self.rdn1[:], [self.ava1]) - for i, ava in enumerate(self.rdn1): - if i == 0: - self.assertEqual(ava, self.ava1) - else: - self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn1))) - - self.assertEqual(len(self.rdn2), 1) - self.assertEqual(self.rdn2[:], [self.ava2]) - for i, ava in enumerate(self.rdn2): - if i == 0: - self.assertEqual(ava, self.ava2) - else: - self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn2))) - - self.assertEqual(len(self.rdn3), 2) - self.assertEqual(self.rdn3[:], [self.ava1, self.ava2]) - for i, ava in enumerate(self.rdn3): - if i == 0: - self.assertEqual(ava, self.ava1) - elif i == 1: - self.assertEqual(ava, self.ava2) - else: - self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn3))) - - - def test_concat(self): - rdn1 = RDN((self.attr1, self.value1)) - rdn2 = RDN((self.attr2, self.value2)) - - # in-place addtion - rdn1 += rdn2 - self.assertEqual(rdn1, self.rdn3) - - rdn1 = RDN((self.attr1, self.value1)) - rdn1 += self.ava2 - self.assertEqual(rdn1, self.rdn3) - - rdn1 = RDN((self.attr1, self.value1)) - rdn1 += self.str_ava2 - self.assertEqual(rdn1, self.rdn3) - - # concatenation - rdn1 = RDN((self.attr1, self.value1)) - rdn3 = rdn1 + rdn2 - self.assertEqual(rdn3, self.rdn3) - - rdn3 = rdn1 + self.ava2 - self.assertEqual(rdn3, self.rdn3) - - rdn3 = rdn1 + self.str_ava2 - self.assertEqual(rdn3, self.rdn3) - - -class TestDN(unittest.TestCase): - def setUp(self): - # ava1 must sort before ava2 - self.attr1 = 'cn' - self.value1 = 'Bob' - self.str_ava1 = '%s=%s' % (self.attr1, self.value1) - self.ava1 = AVA(self.attr1, self.value1) - - self.str_rdn1 = '%s=%s' % (self.attr1, self.value1) - self.rdn1 = RDN((self.attr1, self.value1)) - - self.attr2 = 'ou' - self.value2 = 'people' - self.str_ava2 = '%s=%s' % (self.attr2, self.value2) - self.ava2 = AVA(self.attr2, self.value2) - - self.str_rdn2 = '%s=%s' % (self.attr2, self.value2) - self.rdn2 = RDN((self.attr2, self.value2)) - - self.str_dn1 = self.str_rdn1 - self.dn1 = DN(self.rdn1) - - self.str_dn2 = self.str_rdn2 - self.dn2 = DN(self.rdn2) - - self.str_dn3 = '%s,%s' % (self.str_rdn1, self.str_rdn2) - self.dn3 = DN(self.rdn1, self.rdn2) - - self.base_rdn1 = RDN(('dc', 'redhat')) - self.base_rdn2 = RDN(('dc', 'com')) - self.base_dn = DN(self.base_rdn1, self.base_rdn2) - - self.container_rdn1 = RDN(('cn', 'sudorules')) - self.container_rdn2 = RDN(('cn', 'sudo')) - self.container_dn = DN(self.container_rdn1, self.container_rdn2) - - self.base_container_dn = DN((self.attr1, self.value1), - self.container_dn, self.base_dn) - - - def test_create(self): - # Create with single attr,value pair - dn1 = DN((self.attr1, self.value1)) - self.assertEqual(len(dn1), 1) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - - # Create with single attr,value pair passed as a tuple - dn1 = DN((self.attr1, self.value1)) - self.assertEqual(len(dn1), 1) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - - # Creation with multiple attr,value string pairs should fail - with self.assertRaises(ValueError): - dn1 = DN(self.attr1, self.value1, self.attr2, self.value2) - - # Create with multiple attr,value pairs passed as tuples & lists - dn1 = DN((self.attr1, self.value1), [self.attr2, self.value2]) - self.assertEqual(len(dn1), 2) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - self.assertIsInstance(dn1[1], RDN) - self.assertIsInstance(dn1[1].attr, unicode) - self.assertIsInstance(dn1[1].value, unicode) - self.assertEqual(dn1[1], self.rdn2) - - # Create with multiple attr,value pairs passed as tuple and RDN - dn1 = DN((self.attr1, self.value1), RDN((self.attr2, self.value2))) - self.assertEqual(len(dn1), 2) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - self.assertIsInstance(dn1[1], RDN) - self.assertIsInstance(dn1[1].attr, unicode) - self.assertIsInstance(dn1[1].value, unicode) - self.assertEqual(dn1[1], self.rdn2) - - # Create with multiple attr,value pairs but reverse - # constructor parameter ordering. RDN ordering should also be - # reversed because DN's are a ordered sequence of RDN's - dn1 = DN((self.attr2, self.value2), (self.attr1, self.value1)) - self.assertEqual(len(dn1), 2) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn2) - self.assertIsInstance(dn1[1], RDN) - self.assertIsInstance(dn1[1].attr, unicode) - self.assertIsInstance(dn1[1].value, unicode) - self.assertEqual(dn1[1], self.rdn1) - - # Create with single RDN object - dn1 = DN(self.rdn1) - self.assertEqual(len(dn1), 1) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - - # Create with multiple RDN objects, assure ordering is preserved. - dn1 = DN(self.rdn1, self.rdn2) - self.assertEqual(len(dn1), 2) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - self.assertIsInstance(dn1[1], RDN) - self.assertIsInstance(dn1[1].attr, unicode) - self.assertIsInstance(dn1[1].value, unicode) - self.assertEqual(dn1[1], self.rdn2) - - # Create with multiple RDN objects in different order, assure - # ordering is preserved. - dn1 = DN(self.rdn2, self.rdn1) - self.assertEqual(len(dn1), 2) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn2) - self.assertIsInstance(dn1[1], RDN) - self.assertIsInstance(dn1[1].attr, unicode) - self.assertIsInstance(dn1[1].value, unicode) - self.assertEqual(dn1[1], self.rdn1) - - # Create with single string with 1 RDN - dn1 = DN(self.str_rdn1) - self.assertEqual(len(dn1), 1) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - - # Create with single string with 2 RDN's - dn1 = DN(self.str_dn3) - self.assertEqual(len(dn1), 2) - self.assertIsInstance(dn1[0], RDN) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0], self.rdn1) - self.assertIsInstance(dn1[1], RDN) - self.assertIsInstance(dn1[1].attr, unicode) - self.assertIsInstance(dn1[1].value, unicode) - self.assertEqual(dn1[1], self.rdn2) - - # Create with RDN, and 2 DN's (e.g. attr + container + base) - dn1 = DN((self.attr1, self.value1), self.container_dn, self.base_dn) - self.assertEqual(len(dn1), 5) - dn_str = ','.join([str(self.rdn1), - str(self.container_rdn1), str(self.container_rdn2), - str(self.base_rdn1), str(self.base_rdn2)]) - self.assertEqual(str(dn1), dn_str) - - def test_str(self): - self.assertEqual(str(self.dn1), self.str_dn1) - self.assertIsInstance(str(self.dn1), str) - - self.assertEqual(str(self.dn2), self.str_dn2) - self.assertIsInstance(str(self.dn2), str) - - self.assertEqual(str(self.dn3), self.str_dn3) - self.assertIsInstance(str(self.dn3), str) - - def test_cmp(self): - # Equality - dn1 = DN((self.attr1, self.value1)) - - self.assertTrue(dn1 == self.dn1) - self.assertFalse(dn1 != self.dn1) - - result = cmp(dn1, self.dn1) - self.assertEqual(result, 0) - - # Make dn1's attr greater - dn1[0].attr = self.attr1 + "1" - - self.assertFalse(dn1 == self.dn1) - self.assertTrue(dn1 != self.dn1) - - result = cmp(dn1, self.dn1) - self.assertEqual(result, 1) - - result = cmp(self.dn1, dn1) - self.assertEqual(result, -1) - - # Reset dn1's attr, should be equal again - dn1[0].attr = self.attr1 - - result = cmp(dn1, self.dn1) - self.assertEqual(result, 0) - - # Make dn1's value greater - # attr will be equal, this tests secondary comparision component - dn1[0].value = self.value1 + "1" - - result = cmp(dn1, self.dn1) - self.assertEqual(result, 1) - - result = cmp(self.dn1, dn1) - self.assertEqual(result, -1) - - # Make sure dn's with more rdn's are greater - result = cmp(self.dn1, self.dn3) - self.assertEqual(result, -1) - result = cmp(self.dn3, self.dn1) - self.assertEqual(result, 1) - - # Test startswith, endswith - self.assertTrue(self.base_container_dn.startswith(self.rdn1)) - self.assertTrue(self.base_container_dn.startswith(self.dn1)) - self.assertTrue(self.base_container_dn.startswith(self.dn1 + self.container_dn)) - self.assertFalse(self.base_container_dn.startswith(self.dn2)) - self.assertFalse(self.base_container_dn.startswith(self.rdn2)) - self.assertTrue(self.base_container_dn.startswith((self.dn1))) - self.assertTrue(self.base_container_dn.startswith((self.rdn1))) - self.assertFalse(self.base_container_dn.startswith((self.rdn2))) - self.assertTrue(self.base_container_dn.startswith((self.rdn2, self.rdn1))) - self.assertTrue(self.base_container_dn.startswith((self.dn1, self.dn2))) - - self.assertTrue(self.base_container_dn.endswith(self.base_dn)) - self.assertTrue(self.base_container_dn.endswith(self.container_dn + self.base_dn)) - self.assertFalse(self.base_container_dn.endswith(DN(self.base_rdn1))) - self.assertTrue(self.base_container_dn.endswith(DN(self.base_rdn2))) - self.assertTrue(self.base_container_dn.endswith((DN(self.base_rdn1), DN(self.base_rdn2)))) - - # Test "in" membership - self.assertTrue(self.container_rdn1 in self.container_dn) - self.assertTrue(self.container_dn in self.container_dn) - self.assertFalse(self.base_rdn1 in self.container_dn) - - self.assertTrue(self.container_rdn1 in self.base_container_dn) - self.assertTrue(self.container_dn in self.base_container_dn) - self.assertTrue(self.container_dn + self.base_dn in - self.base_container_dn) - self.assertTrue(self.dn1 + self.container_dn + self.base_dn in - self.base_container_dn) - self.assertTrue(self.dn1 + self.container_dn + self.base_dn == - self.base_container_dn) - - self.assertFalse(self.container_rdn1 in self.base_dn) - - def test_indexing(self): - self.assertEqual(self.dn1[0], self.rdn1) - self.assertEqual(self.dn1[self.rdn1.attr], self.rdn1.value) - with self.assertRaises(KeyError): - self.dn1['foo'] - - self.assertEqual(self.dn2[0], self.rdn2) - self.assertEqual(self.dn2[self.rdn2.attr], self.rdn2.value) - with self.assertRaises(KeyError): - self.dn2['foo'] - - self.assertEqual(self.dn3[0], self.rdn1) - self.assertEqual(self.dn3[self.rdn1.attr], self.rdn1.value) - self.assertEqual(self.dn3[1], self.rdn2) - self.assertEqual(self.dn3[self.rdn2.attr], self.rdn2.value) - with self.assertRaises(KeyError): - self.dn3['foo'] - - with self.assertRaises(TypeError): - self.dn3[1.0] - - def test_assignments(self): - dn_low = 0 - dn_high = 6 - - rdn_args = make_rdn_args(dn_low, dn_high, 'tuple', - default_rdn_attr_arg, default_rdn_value_arg) - dn1 = DN(*rdn_args) - - rdn_args = make_rdn_args(dn_low, dn_high, 'list', - default_rdn_attr_arg, default_rdn_value_arg) - dn2 = DN(*rdn_args) - - rdn_args = make_rdn_args(dn_low, dn_high, 'RDN', - default_rdn_attr_arg, default_rdn_value_arg) - dn3 = DN(*rdn_args) - - self.assertEqual(dn1, dn2) - self.assertEqual(dn1, dn3) - - for i in range(dn_low, dn_high): - attr = default_rdn_attr_arg(i) - value = default_rdn_value_arg(i) - self.assertEqual(dn1[i].attr, attr) - self.assertEqual(dn1[i].value, value) - self.assertEqual(dn1[attr], value) - - for i in range(dn_low, dn_high): - if i % 2: - orig_attr = default_rdn_attr_arg(i) - attr = alt_rdn_attr_arg(i) - value = alt_rdn_value_arg(i) - dn1[i] = attr, value - dn2[orig_attr] = (attr, value) - dn3[i] = RDN((attr, value)) - - self.assertEqual(dn1, dn2) - self.assertEqual(dn1, dn3) - - for i in range(dn_low, dn_high): - if i % 2: - attr = alt_rdn_attr_arg(i) - value = alt_rdn_value_arg(i) - else: - attr = default_rdn_attr_arg(i) - value = default_rdn_value_arg(i) - self.assertEqual(dn1[i].value, dn1[i].value) - self.assertEqual(dn1[attr], value) - - # Slices - slice_low = 2 - slice_high = 4 - interval = range(slice_low, slice_high) - - # Slices - # Assign via tuple - rdn_args = make_rdn_args(dn_low, dn_high, 'tuple', - default_rdn_attr_arg, default_rdn_value_arg) - dn1 = DN(*rdn_args) - - dn_slice = make_rdn_args(slice_low, slice_high, 'tuple', - alt_rdn_attr_arg, alt_rdn_value_arg) - - dn1[slice_low:slice_high] = dn_slice - - for i in range(dn_low, dn_high): - if i in interval: - attr = alt_rdn_attr_arg(i) - value = alt_rdn_value_arg(i) - else: - attr = default_rdn_attr_arg(i) - value = default_rdn_value_arg(i) - self.assertEqual(dn1[i].value, dn1[i].value) - self.assertEqual(dn1[attr], value) - - query_slice = dn1[slice_low:slice_high] - for i, query_rdn in enumerate(query_slice): - slice_rdn = RDN(dn_slice[i]) - self.assertEqual(slice_rdn, query_rdn) - - # insert - dn = DN(self.rdn2) - dn.insert(0, self.rdn1) - self.assertEqual(dn, self.dn3) - - dn = DN(self.rdn1) - dn.insert(1, (self.attr2, self.value2)) - self.assertEqual(dn, self.dn3) - - # Slices - # Assign via RDN - rdn_args = make_rdn_args(dn_low, dn_high, 'tuple', - default_rdn_attr_arg, default_rdn_value_arg) - dn1 = DN(*rdn_args) - - dn_slice = make_rdn_args(slice_low, slice_high, 'RDN', - alt_rdn_attr_arg, alt_rdn_value_arg) - - dn1[slice_low:slice_high] = dn_slice - - for i in range(dn_low, dn_high): - if i in interval: - attr = alt_rdn_attr_arg(i) - value = alt_rdn_value_arg(i) - else: - attr = default_rdn_attr_arg(i) - value = default_rdn_value_arg(i) - self.assertEqual(dn1[i].value, dn1[i].value) - self.assertEqual(dn1[attr], value) - - query_slice = dn1[slice_low:slice_high] - for i, query_rdn in enumerate(query_slice): - slice_rdn = dn_slice[i] - self.assertEqual(slice_rdn, query_rdn) - - - def test_iter(self): - self.assertEqual(len(self.dn1), 1) - self.assertEqual(self.dn1[:], [self.rdn1]) - for i, ava in enumerate(self.dn1): - if i == 0: - self.assertEqual(ava, self.rdn1) - else: - self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn1))) - - self.assertEqual(len(self.dn2), 1) - self.assertEqual(self.dn2[:], [self.rdn2]) - for i, ava in enumerate(self.dn2): - if i == 0: - self.assertEqual(ava, self.rdn2) - else: - self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn2))) - - self.assertEqual(len(self.dn3), 2) - self.assertEqual(self.dn3[:], [self.rdn1, self.rdn2]) - for i, ava in enumerate(self.dn3): - if i == 0: - self.assertEqual(ava, self.rdn1) - elif i == 1: - self.assertEqual(ava, self.rdn2) - else: - self.fail("got iteration index %d, but len=%d" % (i, len(self.dn3))) - - - def test_concat(self): - dn1 = DN((self.attr1, self.value1)) - dn2 = DN([self.attr2, self.value2]) - - # in-place addtion - dn1 += dn2 - self.assertEqual(dn1, self.dn3) - - dn1 = DN((self.attr1, self.value1)) - dn1 += self.rdn2 - self.assertEqual(dn1, self.dn3) - - dn1 = DN((self.attr1, self.value1)) - dn1 += self.dn2 - self.assertEqual(dn1, self.dn3) - - dn1 = DN((self.attr1, self.value1)) - dn1 += self.str_dn2 - self.assertEqual(dn1, self.dn3) - - # concatenation - dn1 = DN((self.attr1, self.value1)) - dn3 = dn1 + dn2 - self.assertEqual(dn3, self.dn3) - - dn1 = DN((self.attr1, self.value1)) - dn3 = dn1 + self.rdn2 - self.assertEqual(dn3, self.dn3) - - dn3 = dn1 + self.str_rdn2 - self.assertEqual(dn3, self.dn3) - - dn3 = dn1 + self.str_dn2 - self.assertEqual(dn3, self.dn3) - - dn3 = dn1 + self.dn2 - self.assertEqual(dn3, self.dn3) - -class TestEscapes(unittest.TestCase): - def setUp(self): - self.privilege = 'R,W privilege' - self.dn_str_hex_escape = 'cn=R\\2cW privilege,cn=privileges,cn=pbac,dc=idm,dc=lab,dc=bos,dc=redhat,dc=com' - self.dn_str_backslash_escape = 'cn=R\\,W privilege,cn=privileges,cn=pbac,dc=idm,dc=lab,dc=bos,dc=redhat,dc=com' - - def test_escape(self): - dn = DN(self.dn_str_hex_escape) - self.assertEqual(dn['cn'], self.privilege) - self.assertEqual(dn[0].value, self.privilege) - - dn = DN(self.dn_str_backslash_escape) - self.assertEqual(dn['cn'], self.privilege) - self.assertEqual(dn[0].value, self.privilege) - -class TestInternationalization(unittest.TestCase): - def setUp(self): - # Hello in Arabic - self.arabic_hello_utf8 = '\xd9\x85\xd9\x83\xd9\x8a\xd9\x84' + \ - '\xd8\xb9\x20\xd9\x85\xd8\xa7\xd9' + \ - '\x84\xd9\x91\xd8\xb3\xd9\x84\xd8\xa7' - - self.arabic_hello_unicode = self.arabic_hello_utf8.decode('utf-8') - - def test_i18n(self): - self.assertEqual(self.arabic_hello_utf8, - self.arabic_hello_unicode.encode('utf-8')) - - # AVA's - # test attr i18n - ava1 = AVA(self.arabic_hello_unicode, 'foo') - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - self.assertEqual(ava1.attr, self.arabic_hello_unicode) - self.assertEqual(str(ava1), self.arabic_hello_utf8+'=foo') - - ava1 = AVA(self.arabic_hello_utf8, 'foo') - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - self.assertEqual(ava1.attr, self.arabic_hello_unicode) - self.assertEqual(str(ava1), self.arabic_hello_utf8+'=foo') - - # test value i18n - ava1 = AVA('cn', self.arabic_hello_unicode) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - self.assertEqual(ava1.value, self.arabic_hello_unicode) - self.assertEqual(str(ava1), 'cn='+self.arabic_hello_utf8) - - ava1 = AVA('cn', self.arabic_hello_utf8) - self.assertIsInstance(ava1.attr, unicode) - self.assertIsInstance(ava1.value, unicode) - self.assertEqual(ava1.value, self.arabic_hello_unicode) - self.assertEqual(str(ava1), 'cn='+self.arabic_hello_utf8) - - # RDN's - # test attr i18n - rdn1 = RDN((self.arabic_hello_unicode, 'foo')) - self.assertIsInstance(rdn1.attr, unicode) - self.assertIsInstance(rdn1.value, unicode) - self.assertEqual(rdn1.attr, self.arabic_hello_unicode) - self.assertEqual(str(rdn1), self.arabic_hello_utf8+'=foo') - - rdn1 = RDN((self.arabic_hello_utf8, 'foo')) - self.assertIsInstance(rdn1.attr, unicode) - self.assertIsInstance(rdn1.value, unicode) - self.assertEqual(rdn1.attr, self.arabic_hello_unicode) - self.assertEqual(str(rdn1), self.arabic_hello_utf8+'=foo') - - # test value i18n - rdn1 = RDN(('cn', self.arabic_hello_unicode)) - self.assertIsInstance(rdn1.attr, unicode) - self.assertIsInstance(rdn1.value, unicode) - self.assertEqual(rdn1.value, self.arabic_hello_unicode) - self.assertEqual(str(rdn1), 'cn='+self.arabic_hello_utf8) - - rdn1 = RDN(('cn', self.arabic_hello_utf8)) - self.assertIsInstance(rdn1.attr, unicode) - self.assertIsInstance(rdn1.value, unicode) - self.assertEqual(rdn1.value, self.arabic_hello_unicode) - self.assertEqual(str(rdn1), 'cn='+self.arabic_hello_utf8) - - # DN's - # test attr i18n - dn1 = DN((self.arabic_hello_unicode, 'foo')) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0].attr, self.arabic_hello_unicode) - self.assertEqual(str(dn1), self.arabic_hello_utf8+'=foo') - - dn1 = DN((self.arabic_hello_utf8, 'foo')) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0].attr, self.arabic_hello_unicode) - self.assertEqual(str(dn1), self.arabic_hello_utf8+'=foo') - - # test value i18n - dn1 = DN(('cn', self.arabic_hello_unicode)) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0].value, self.arabic_hello_unicode) - self.assertEqual(str(dn1), 'cn='+self.arabic_hello_utf8) - - dn1 = DN(('cn', self.arabic_hello_utf8)) - self.assertIsInstance(dn1[0].attr, unicode) - self.assertIsInstance(dn1[0].value, unicode) - self.assertEqual(dn1[0].value, self.arabic_hello_unicode) - self.assertEqual(str(dn1), 'cn='+self.arabic_hello_utf8) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_ipalib/test_encoder.py b/tests/test_ipalib/test_encoder.py deleted file mode 100644 index 430eda047..000000000 --- a/tests/test_ipalib/test_encoder.py +++ /dev/null @@ -1,149 +0,0 @@ -# encoding: utf-8 -# Authors: -# Pavel Zuna -# -# Copyright (C) 2009 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 . -""" -Test the `ipalib.encoder` module. -""" - -import string - -from tests.util import ClassChecker, assert_equal -from ipalib.encoder import Encoder, EncoderSettings, encode_args, decode_retval - -_encoder_settings = EncoderSettings() - -_test_str_d = u'ěščřžýáíé' -_test_str_e = u'ěščřžýáíé'.encode(_encoder_settings.encode_to) - - -class test_Encoder(ClassChecker): - """ - Test the `ipalib.encoder.Encoder` class. - """ - _cls = Encoder - - def test_encode(self): - """ - Test the `ipalib.encoder.Encoder.encode` method. - """ - o = self.cls() - encode_to = o.encoder_settings.encode_to - o.encoder_settings.encode_postprocessor = lambda x: x - # strings - assert_equal(o.encode('ahoj'), 'ahoj'.encode(encode_to)) - assert_equal(o.encode(_test_str_d), _test_str_e) - # bool, float, int, long - assert_equal(o.encode(True), 'TRUE'.encode(encode_to)) - assert_equal(o.encode(1.01), str(1.01).encode(encode_to)) - assert_equal(o.encode(1000), str(1000).encode(encode_to)) - assert_equal(o.encode(long(1)), str(long(1)).encode(encode_to)) - # lists - expected = [_test_str_e, '1'] - assert_equal(o.encode([_test_str_d, 1]), expected) - expected = ['1', ['1', '2', '3']] - assert_equal(o.encode([1, [1, 2, 3]]), expected) - # tuples - expected = (_test_str_e, '1') - assert_equal(o.encode((_test_str_d, 1)), expected) - expected = ('1', ('1', '2', '3')) - assert_equal(o.encode((1, (1, 2, 3))), expected) - # dicts: only values, no postprocessing - o.encoder_settings.encode_dict_keys = False - o.encoder_settings.encode_dict_keys_postprocess = False - o.encoder_settings.encode_dict_vals = True - o.encoder_settings.encode_dict_vals_postprocess = False - expected = {_test_str_d: _test_str_e} - assert_equal(o.encode({_test_str_d: _test_str_d}), expected) - # dicts: only keys, no postprocessing - o.encoder_settings.encode_dict_keys = True - o.encoder_settings.encode_dict_vals = False - expected = {_test_str_e: _test_str_d} - assert_equal(o.encode({_test_str_d: _test_str_d}), expected) - # dicts: both keys and values, no postprocessing - o.encoder_settings.encode_dict_vals = True - expected = {_test_str_e: _test_str_e} - assert_equal(o.encode({_test_str_d: _test_str_d}), expected) - # dicts: both keys and values, postprocessing on keys only - o.encoder_settings.encode_dict_keys = True - o.encoder_settings.encode_dict_keys_postprocess = True - o.encoder_settings.encode_postprocessor = string.upper - expected = {_test_str_e.upper(): _test_str_e} - assert_equal(o.encode({u'ěščřžýáíé': u'ěščřžýáíé'}), expected) - # None - o.encoder_settings.encode_postprocessor = lambda x: x - o.encoder_settings.encode_none = False - assert_equal(o.encode(None), None) - o.encoder_settings.encode_none = True - assert_equal(o.encode(None), str(None).encode(encode_to)) - - def test_decode(self): - """ - Test the `ipalib.encoder.Encoder.decode` method. - """ - o = self.cls() - decode_from = o.encoder_settings.decode_from - o.encoder_settings.decode_postprocessor = lambda x: x - # strings - assert_equal(o.decode('ahoj'), 'ahoj'.decode(decode_from)) - assert_equal(o.decode(_test_str_e), _test_str_d) - # bool, float, int, long - assert_equal(o.decode('True'), str(True).decode(decode_from)) - assert_equal(o.decode('1.01'), str(1.01).decode(decode_from)) - assert_equal(o.decode('1000'), str(1000).decode(decode_from)) - assert_equal(o.decode('1'), str(long(1)).decode(decode_from)) - # lists - expected = [_test_str_d, '1'] - assert_equal(o.decode([_test_str_e, '1']), expected) - expected = [u'1', [u'1', u'2', u'3']] - assert_equal(o.decode(['1', ['1', '2', '3']]), expected) - # tuples - expected = (_test_str_d, 1) - assert_equal(o.decode((_test_str_e, 1)), expected) - expected = (u'1', (u'1', u'2', u'3')) - assert_equal(o.decode(('1', ('1', '2', '3'))), expected) - # dicts: only values, no postprocessing - o.encoder_settings.decode_dict_keys = False - o.encoder_settings.decode_dict_keys_postprocess = False - o.encoder_settings.decode_dict_vals = True - o.encoder_settings.decode_dict_vals_postprocess = False - expected = {_test_str_e: _test_str_d} - assert_equal(o.decode({_test_str_e: _test_str_e}), expected) - # dicts: only keys, no postprocessing - o.encoder_settings.decode_dict_keys = True - o.encoder_settings.decode_dict_vals = False - expected = {_test_str_d: _test_str_e} - assert_equal(o.decode({_test_str_e: _test_str_e}), expected) - # dicts: both keys and values, no postprocessing - o.encoder_settings.decode_dict_vals = True - expected = {_test_str_d: _test_str_d} - assert_equal(o.decode({_test_str_e: _test_str_e}), expected) - # dicts: both keys and values, postprocessing on keys only - o.encoder_settings.decode_dict_keys = True - o.encoder_settings.decode_dict_keys_postprocess = True - o.encoder_settings.decode_postprocessor = string.upper - expected = {_test_str_d.upper(): _test_str_d} - assert_equal(o.decode({_test_str_e: _test_str_e}), expected) - # TODO: dict decoding using a table - # None - o.encoder_settings.decode_postprocessor = lambda x: x - o.encoder_settings.decode_none = False - assert_equal(o.decode(None), None) - o.encoder_settings.decode_none = True - assert_equal(o.decode(None), str(None).decode(decode_from)) - diff --git a/tests/test_ipalib/test_x509.py b/tests/test_ipalib/test_x509.py index a2e9b2265..cf076313e 100644 --- a/tests/test_ipalib/test_x509.py +++ b/tests/test_ipalib/test_x509.py @@ -32,7 +32,7 @@ from ipalib.constants import NAME_REGEX, NAME_ERROR import base64 from ipalib import x509 from nss.error import NSPRError -from ipalib.dn import * +from ipapython.dn import DN # certutil - diff --git a/tests/test_ipapython/test_dn.py b/tests/test_ipapython/test_dn.py new file mode 100644 index 000000000..441affa4f --- /dev/null +++ b/tests/test_ipapython/test_dn.py @@ -0,0 +1,1937 @@ +#!/usr/bin/python + +import unittest +from ipapython.dn import * + +def default_rdn_attr_arg(i): + return 'a%d' % i + +def default_rdn_value_arg(i): + return str(i) + +def alt_rdn_attr_arg(i): + return 'b%d' % i + +def alt_rdn_value_arg(i): + return str(i*10) + +def make_rdn_args(low, high, kind, attr=None, value=None): + result=[] + for i in range(low, high): + if attr is None: + new_attr = default_rdn_attr_arg(i) + elif callable(attr): + new_attr = attr(i) + else: + new_attr = attr + + if value is None: + new_value = default_rdn_value_arg(i) + elif callable(value): + new_value = value(i) + else: + new_value = value + + if kind == 'tuple': + result.append((new_attr, new_value)) + elif kind == 'list': + result.append([new_attr, new_value]) + elif kind == 'RDN': + result.append(RDN((new_attr, new_value))) + else: + raise ValueError("Unknown kind = %s" % kind) + + return result + +def expected_class(klass, component): + if klass is AVA: + if component == 'self': + return AVA + + elif klass is EditableAVA: + if component == 'self': + return EditableAVA + + elif klass is RDN: + if component == 'self': + return RDN + elif component == 'AVA': + return AVA + + elif klass is EditableRDN: + if component == 'self': + return EditableRDN + elif component == 'AVA': + return EditableAVA + + elif klass is DN: + if component == 'self': + return DN + elif component == 'AVA': + return AVA + elif component == 'RDN': + return RDN + + elif klass is EditableDN: + if component == 'self': + return EditableDN + elif component == 'AVA': + return EditableAVA + elif component == 'RDN': + return EditableRDN + + raise ValueError("class %s with component '%s' unknown" % (klass.__name__, component)) + + +class TestAVA(unittest.TestCase): + def setUp(self): + self.attr1 = 'cn' + self.value1 = 'Bob' + self.str_ava1 = '%s=%s' % (self.attr1, self.value1) + self.ava1 = AVA(self.attr1, self.value1) + + self.attr2 = 'ou' + self.value2 = 'People' + self.str_ava2 = '%s=%s' % (self.attr2, self.value2) + self.ava2 = AVA(self.attr2, self.value2) + + self.attr3 = 'c' + self.value3 = 'US' + self.str_ava3 = '%s=%s' % (self.attr3, self.value3) + self.ava3 = AVA(self.attr3, self.value3) + + def assertExpectedClass(self, klass, obj, component): + self.assertIs(obj.__class__, expected_class(klass, component)) + + def test_create(self): + for AVA_class in (AVA, EditableAVA): + # Create with attr,value pair + ava1 = AVA_class(self.attr1, self.value1) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1, self.ava1) + + # Create with "attr=value" string + ava1 = AVA_class(self.str_ava1) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1, self.ava1) + + # Create with tuple (attr, value) + ava1 = AVA_class((self.attr1, self.value1)) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1, self.ava1) + + # Create with list [attr, value] + ava1 = AVA_class([self.attr1, self.value1]) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1, self.ava1) + + # Create with no args should fail + with self.assertRaises(TypeError): + AVA_class() + + # Create with more than 2 args should fail + with self.assertRaises(TypeError): + AVA_class(self.attr1, self.value1, self.attr1) + + # Create with 1 arg which is not string should fail + with self.assertRaises(TypeError): + AVA_class(1) + + # Create with malformed AVA_class string should fail + with self.assertRaises(ValueError): + AVA_class("cn") + + # Create with non-string parameters, should convert + ava1 = AVA_class(1, self.value1) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1.attr, u'1') + + ava1 = AVA_class((1, self.value1)) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1.attr, u'1') + + ava1 = AVA_class(self.attr1, 1) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1.value, u'1') + + ava1 = AVA_class((self.attr1, 1)) + self.assertExpectedClass(AVA_class, ava1, 'self') + self.assertEqual(ava1.value, u'1') + + def test_indexing(self): + for AVA_class in (AVA, EditableAVA): + ava1 = AVA_class(self.ava1) + + self.assertEqual(ava1[self.attr1], self.value1) + + with self.assertRaises(KeyError): + ava1['foo'] + + with self.assertRaises(TypeError): + ava1[0] + + def test_properties(self): + for AVA_class in (AVA, EditableAVA): + ava1 = AVA_class(self.ava1) + + self.assertEqual(ava1.attr, self.attr1) + self.assertIsInstance(ava1.attr, unicode) + + self.assertEqual(ava1.value, self.value1) + self.assertIsInstance(ava1.value, unicode) + + def test_str(self): + for AVA_class in (AVA, EditableAVA): + ava1 = AVA_class(self.ava1) + + self.assertEqual(str(ava1), self.str_ava1) + self.assertIsInstance(str(ava1), str) + + def test_cmp(self): + for AVA_class in (AVA, EditableAVA): + # Equality + ava1 = AVA_class(self.attr1, self.value1) + + self.assertTrue(ava1 == self.ava1) + self.assertFalse(ava1 != self.ava1) + + self.assertTrue(ava1 == self.str_ava1) + self.assertFalse(ava1 != self.str_ava1) + + result = cmp(ava1, self.ava1) + self.assertEqual(result, 0) + + # Upper case attr should still be equal + ava1 = AVA_class(self.attr1.upper(), self.value1) + + self.assertFalse(ava1.attr == self.attr1) + self.assertTrue(ava1.value == self.value1) + self.assertTrue(ava1 == self.ava1) + self.assertFalse(ava1 != self.ava1) + + result = cmp(ava1, self.ava1) + self.assertEqual(result, 0) + + # Upper case value should still be equal + ava1 = AVA_class(self.attr1, self.value1.upper()) + + self.assertTrue(ava1.attr == self.attr1) + self.assertFalse(ava1.value == self.value1) + self.assertTrue(ava1 == self.ava1) + self.assertFalse(ava1 != self.ava1) + + result = cmp(ava1, self.ava1) + self.assertEqual(result, 0) + + # Make ava1's attr greater + if AVA_class.is_mutable: + ava1.attr = self.attr1 + "1" + else: + with self.assertRaises(AttributeError): + ava1.attr = self.attr1 + "1" + ava1 = AVA_class(self.attr1 + "1", self.value1.upper()) + + self.assertFalse(ava1 == self.ava1) + self.assertTrue(ava1 != self.ava1) + + result = cmp(ava1, self.ava1) + self.assertEqual(result, 1) + + result = cmp(self.ava1, ava1) + self.assertEqual(result, -1) + + # Reset ava1's attr, should be equal again + if AVA_class.is_mutable: + ava1.attr = self.attr1 + else: + with self.assertRaises(AttributeError): + ava1.attr = self.attr1 + ava1 = AVA_class(self.attr1, self.value1.upper()) + + result = cmp(ava1, self.ava1) + self.assertEqual(result, 0) + + # Make ava1's value greater + # attr will be equal, this tests secondary comparision component + if AVA_class.is_mutable: + ava1.value = self.value1 + "1" + else: + with self.assertRaises(AttributeError): + ava1.value = self.value1 + "1" + ava1 = AVA_class(self.attr1, self.value1 + "1") + + result = cmp(ava1, self.ava1) + self.assertEqual(result, 1) + + result = cmp(self.ava1, ava1) + self.assertEqual(result, -1) + + def test_hashing(self): + # create AVA's that have the same value + immutable_ava1 = AVA((self.attr1, self.value1)) + immutable_ava2 = AVA((self.attr1, self.value1)) + + mutable_ava1 = EditableAVA((self.attr1, self.value1)) + mutable_ava2 = EditableAVA((self.attr1, self.value1)) + + # Immutable AVA's that are equal should hash to the same value. + # Mutable AVA's should not be hashable. + + self.assertEqual(immutable_ava1, immutable_ava2) + self.assertEqual(immutable_ava1, mutable_ava1) + self.assertEqual(immutable_ava1, mutable_ava2) + self.assertEqual(mutable_ava1, immutable_ava2) + + # Good, everyone's equal, now verify their hash values + + self.assertEqual(hash(immutable_ava1), hash(immutable_ava2)) + with self.assertRaises(TypeError): + hash(mutable_ava1) + with self.assertRaises(TypeError): + hash(mutable_ava2) + + # Different immutable AVA objects with the same value should + # map to 1 common key and 1 member in a set. The key and + # member are based on the object's value. + # + # Mutable AVA objects should be unhashable. + + for AVA_class in (AVA, EditableAVA): + ava1_a = AVA_class(self.ava1) + ava1_b = AVA_class(self.ava1) + + ava2_a = AVA_class(self.ava2) + ava2_b = AVA_class(self.ava2) + + ava3_a = AVA_class(self.ava3) + ava3_b = AVA_class(self.ava3) + + self.assertEqual(ava1_a, ava1_b) + self.assertEqual(ava2_a, ava2_b) + self.assertEqual(ava3_a, ava3_b) + + d = dict() + s = set() + + if AVA_class.is_mutable: + with self.assertRaises(TypeError): + d[ava1_a] = str(ava1_a) + with self.assertRaises(TypeError): + d[ava1_b] = str(ava1_b) + with self.assertRaises(TypeError): + d[ava2_a] = str(ava2_a) + with self.assertRaises(TypeError): + d[ava2_b] = str(ava2_b) + + with self.assertRaises(TypeError): + s.add(ava1_a) + with self.assertRaises(TypeError): + s.add(ava1_b) + with self.assertRaises(TypeError): + s.add(ava2_a) + with self.assertRaises(TypeError): + s.add(ava2_b) + else: + d[ava1_a] = str(ava1_a) + d[ava1_b] = str(ava1_b) + d[ava2_a] = str(ava2_a) + d[ava2_b] = str(ava2_b) + + s.add(ava1_a) + s.add(ava1_b) + s.add(ava2_a) + s.add(ava2_b) + + self.assertEqual(len(d), 2) + self.assertEqual(len(s), 2) + self.assertEqual(sorted(d.keys()), sorted([ava1_a, ava2_a])) + self.assertEqual(sorted(s), sorted([ava1_a, ava2_a])) + + self.assertTrue(ava1_a in d) + self.assertTrue(ava1_b in d) + self.assertTrue(ava2_a in d) + self.assertTrue(ava2_b in d) + self.assertFalse(ava3_a in d) + self.assertFalse(ava3_b in d) + + self.assertTrue(d.has_key(ava1_a)) + self.assertTrue(d.has_key(ava1_b)) + self.assertTrue(d.has_key(ava2_a)) + self.assertTrue(d.has_key(ava2_b)) + self.assertFalse(d.has_key(ava3_a)) + self.assertFalse(d.has_key(ava3_b)) + + self.assertTrue(ava1_a in s) + self.assertTrue(ava1_b in s) + self.assertTrue(ava2_a in s) + self.assertTrue(ava2_b in s) + self.assertFalse(ava3_a in s) + self.assertFalse(ava3_b in s) + + def test_coerce(self): + # Coerce an immutable to a mutable + immutable_ava1 = AVA(self.ava1) + mutable_ava1 = EditableAVA(immutable_ava1) + self.assertEqual(mutable_ava1, self.ava1) + self.assertEqual(mutable_ava1, immutable_ava1) + + # Coerce a mutable to an immutable + mutable_ava1 = EditableAVA(self.ava1) + immutable_ava1 = AVA(mutable_ava1) + self.assertEqual(immutable_ava1, self.ava1) + self.assertEqual(immutable_ava1, mutable_ava1) + +class TestRDN(unittest.TestCase): + def setUp(self): + # ava1 must sort before ava2 + self.attr1 = 'cn' + self.value1 = 'Bob' + self.str_ava1 = '%s=%s' % (self.attr1, self.value1) + self.ava1 = AVA(self.attr1, self.value1) + + self.str_rdn1 = '%s=%s' % (self.attr1, self.value1) + self.rdn1 = RDN((self.attr1, self.value1)) + + self.attr2 = 'ou' + self.value2 = 'people' + self.str_ava2 = '%s=%s' % (self.attr2, self.value2) + self.ava2 = AVA(self.attr2, self.value2) + + self.str_rdn2 = '%s=%s' % (self.attr2, self.value2) + self.rdn2 = RDN((self.attr2, self.value2)) + + self.str_ava3 = '%s=%s+%s=%s' % (self.attr1, self.value1, self.attr2, self.value2) + + self.str_rdn3 = '%s=%s+%s=%s' % (self.attr1, self.value1, self.attr2, self.value2) + self.rdn3 = RDN(self.ava1, self.ava2) + + def assertExpectedClass(self, klass, obj, component): + self.assertIs(obj.__class__, expected_class(klass, component)) + + def test_create(self): + for RDN_class in (RDN, EditableRDN): + # Create with single attr,value pair + rdn1 = RDN_class((self.attr1, self.value1)) + + + self.assertEqual(len(rdn1), 1) + self.assertEqual(rdn1, self.rdn1) + self.assertExpectedClass(RDN_class, rdn1, 'self') + for i in range(0, len(rdn1)): + self.assertExpectedClass(RDN_class, rdn1[i], 'AVA') + self.assertEqual(rdn1[0], self.ava1) + + # Create with multiple attr,value pairs + rdn3 = RDN_class((self.attr1, self.value1), (self.attr2, self.value2)) + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[1], self.ava2) + + # Create with multiple attr,value pairs passed as lists + rdn3 = RDN_class([self.attr1, self.value1], [self.attr2, self.value2]) + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[1], self.ava2) + + # Create with multiple attr,value pairs but reverse + # constructor parameter ordering. RDN canonical ordering + # should remain the same + rdn3 = RDN_class((self.attr2, self.value2), (self.attr1, self.value1)) + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[1], self.ava2) + + # Create with single AVA object + rdn1 = RDN_class(self.ava1) + self.assertEqual(len(rdn1), 1) + self.assertEqual(rdn1, self.rdn1) + self.assertExpectedClass(RDN_class, rdn1, 'self') + for i in range(0, len(rdn1)): + self.assertExpectedClass(RDN_class, rdn1[i], 'AVA') + self.assertEqual(rdn1[0], self.ava1) + + # Create with multiple AVA objects + rdn3 = RDN_class(self.ava1, self.ava2) + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[1], self.ava2) + + + # Create with multiple AVA objects but reverse constructor + # parameter ordering. RDN canonical ordering should remain + # the same + rdn3 = RDN_class(self.ava2, self.ava1) + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[1], self.ava2) + + # Create with single string with 1 AVA + rdn1 = RDN_class(self.str_rdn1) + self.assertEqual(len(rdn1), 1) + self.assertEqual(rdn1, self.rdn1) + self.assertExpectedClass(RDN_class, rdn1, 'self') + for i in range(0, len(rdn1)): + self.assertExpectedClass(RDN_class, rdn1[i], 'AVA') + self.assertEqual(rdn1[0], self.ava1) + + # Create with single string with 2 AVA's + rdn3 = RDN_class(self.str_rdn3) + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[1], self.ava2) + + def test_properties(self): + for RDN_class in (RDN, EditableRDN): + rdn1 = RDN_class(self.rdn1) + rdn2 = RDN_class(self.rdn2) + rdn3 = RDN_class(self.rdn3) + + self.assertEqual(rdn1.attr, self.attr1) + self.assertIsInstance(rdn1.attr, unicode) + + self.assertEqual(rdn1.value, self.value1) + self.assertIsInstance(rdn1.value, unicode) + + self.assertEqual(rdn2.attr, self.attr2) + self.assertIsInstance(rdn2.attr, unicode) + + self.assertEqual(rdn2.value, self.value2) + self.assertIsInstance(rdn2.value, unicode) + + self.assertEqual(rdn3.attr, self.attr1) + self.assertIsInstance(rdn3.attr, unicode) + + self.assertEqual(rdn3.value, self.value1) + self.assertIsInstance(rdn3.value, unicode) + + def test_str(self): + for RDN_class in (RDN, EditableRDN): + rdn1 = RDN_class(self.rdn1) + rdn2 = RDN_class(self.rdn2) + rdn3 = RDN_class(self.rdn3) + + self.assertEqual(str(rdn1), self.str_rdn1) + self.assertIsInstance(str(rdn1), str) + + self.assertEqual(str(rdn2), self.str_rdn2) + self.assertIsInstance(str(rdn2), str) + + self.assertEqual(str(rdn3), self.str_rdn3) + self.assertIsInstance(str(rdn3), str) + + def test_cmp(self): + for RDN_class in (RDN, EditableRDN): + # Equality + rdn1 = RDN_class((self.attr1, self.value1)) + + self.assertTrue(rdn1 == self.rdn1) + self.assertFalse(rdn1 != self.rdn1) + + self.assertTrue(rdn1 == self.str_rdn1) + self.assertFalse(rdn1 != self.str_rdn1) + + result = cmp(rdn1, self.rdn1) + self.assertEqual(result, 0) + + # Make rdn1's attr greater + if RDN_class.is_mutable: + rdn1.attr = self.attr1 + "1" + else: + rdn1 = RDN_class((self.attr1 + "1", self.value1)) + + self.assertFalse(rdn1 == self.rdn1) + self.assertTrue(rdn1 != self.rdn1) + + result = cmp(rdn1, self.rdn1) + self.assertEqual(result, 1) + + result = cmp(self.rdn1, rdn1) + self.assertEqual(result, -1) + + # Reset rdn1's attr, should be equal again + if RDN_class.is_mutable: + rdn1.attr = self.attr1 + else: + rdn1 = RDN_class((self.attr1, self.value1)) + + result = cmp(rdn1, self.rdn1) + self.assertEqual(result, 0) + + # Make rdn1's value greater + # attr will be equal, this tests secondary comparision component + if RDN_class.is_mutable: + rdn1.value = self.value1 + "1" + else: + rdn1 = RDN_class((self.attr1, self.value1 + "1")) + + result = cmp(rdn1, self.rdn1) + self.assertEqual(result, 1) + + result = cmp(self.rdn1, rdn1) + self.assertEqual(result, -1) + + # Make sure rdn's with more ava's are greater + result = cmp(self.rdn1, self.rdn3) + self.assertEqual(result, -1) + result = cmp(self.rdn3, self.rdn1) + self.assertEqual(result, 1) + + def test_indexing(self): + for RDN_class in (RDN, EditableRDN): + rdn1 = RDN_class(self.rdn1) + rdn2 = RDN_class(self.rdn2) + rdn3 = RDN_class(self.rdn3) + + self.assertEqual(rdn1[0], self.ava1) + self.assertEqual(rdn1[self.ava1.attr], self.ava1.value) + with self.assertRaises(KeyError): + rdn1['foo'] + + self.assertEqual(rdn2[0], self.ava2) + self.assertEqual(rdn2[self.ava2.attr], self.ava2.value) + with self.assertRaises(KeyError): + rdn2['foo'] + + self.assertEqual(rdn3[0], self.ava1) + self.assertEqual(rdn3[self.ava1.attr], self.ava1.value) + self.assertEqual(rdn3[1], self.ava2) + self.assertEqual(rdn3[self.ava2.attr], self.ava2.value) + with self.assertRaises(KeyError): + rdn3['foo'] + + self.assertEqual(rdn1.attr, self.attr1) + self.assertEqual(rdn1.value, self.value1) + + with self.assertRaises(TypeError): + rdn3[1.0] + + # Slices + self.assertEqual(rdn3[0:1], [self.ava1]) + self.assertEqual(rdn3[:], [self.ava1, self.ava2]) + + def test_assignments(self): + for RDN_class in (RDN, EditableRDN): + rdn = RDN_class((self.attr1, self.value1)) + if RDN_class.is_mutable: + rdn[0] = self.ava2 + self.assertEqual(rdn, self.rdn2) + else: + with self.assertRaises(TypeError): + rdn[0] = self.ava2 + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + if RDN_class.is_mutable: + rdn[0] = (self.attr2, self.value2) + self.assertEqual(rdn, self.rdn2) + else: + with self.assertRaises(TypeError): + rdn[0] = (self.attr2, self.value2) + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + if RDN_class.is_mutable: + rdn[self.attr1] = self.str_ava2 + self.assertEqual(rdn[0], self.ava2) + else: + with self.assertRaises(TypeError): + rdn[self.attr1] = self.str_ava2 + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + # Can't assign multiples to single entry + rdn = RDN_class((self.attr1, self.value1)) + with self.assertRaises(TypeError): + rdn[self.attr1] = self.str_ava3 + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + with self.assertRaises(TypeError): + rdn[self.attr1] = (self.attr1, self.value1, self.attr2, self.value2) + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + with self.assertRaises(TypeError): + rdn[self.attr1] = [(self.attr1, self.value1), (self.attr2, self.value2)] + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + # Slices + rdn = RDN_class((self.attr1, self.value1)) + self.assertEqual(rdn, self.rdn1) + if RDN_class.is_mutable: + rdn[0:1] = [self.ava2] + self.assertEqual(rdn, self.rdn2) + else: + with self.assertRaises(TypeError): + rdn[0:1] = [self.ava2] + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + self.assertEqual(rdn, self.rdn1) + if RDN_class.is_mutable: + rdn[:] = [(self.attr2, self.value2)] + self.assertEqual(rdn, self.rdn2) + else: + with self.assertRaises(TypeError): + rdn[:] = [(self.attr2, self.value2)] + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + self.assertEqual(rdn, self.rdn1) + if RDN_class.is_mutable: + rdn[:] = [(self.attr1, self.value1),(self.attr2, self.value2)] + self.assertEqual(rdn, self.rdn3) + else: + with self.assertRaises(TypeError): + rdn[:] = [(self.attr1, self.value1),(self.attr2, self.value2)] + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + rdn = RDN_class((self.attr1, self.value1)) + self.assertEqual(rdn, self.rdn1) + if RDN_class.is_mutable: + rdn[0:1] = [(self.attr1, self.value1), (self.attr2, self.value2)] + self.assertEqual(rdn, self.rdn3) + else: + with self.assertRaises(TypeError): + rdn[0:1] = [(self.attr1, self.value1), (self.attr2, self.value2)] + self.assertExpectedClass(RDN_class, rdn, 'self') + for i in range(0, len(rdn)): + self.assertExpectedClass(RDN_class, rdn[i], 'AVA') + + + def test_iter(self): + for RDN_class in (RDN, EditableRDN): + rdn1 = RDN_class(self.rdn1) + rdn2 = RDN_class(self.rdn2) + rdn3 = RDN_class(self.rdn3) + + self.assertEqual(len(rdn1), 1) + self.assertEqual(rdn1[:], [self.ava1]) + for i, ava in enumerate(rdn1): + if i == 0: + self.assertEqual(ava, self.ava1) + else: + self.fail("got iteration index %d, but len=%d" % (i, len(rdn1))) + + self.assertEqual(len(rdn2), 1) + self.assertEqual(rdn2[:], [self.ava2]) + for i, ava in enumerate(rdn2): + if i == 0: + self.assertEqual(ava, self.ava2) + else: + self.fail("got iteration index %d, but len=%d" % (i, len(rdn2))) + + self.assertEqual(len(rdn3), 2) + self.assertEqual(rdn3[:], [self.ava1, self.ava2]) + for i, ava in enumerate(rdn3): + if i == 0: + self.assertEqual(ava, self.ava1) + elif i == 1: + self.assertEqual(ava, self.ava2) + else: + self.fail("got iteration index %d, but len=%d" % (i, len(rdn3))) + + + def test_concat(self): + for RDN_class in (RDN, EditableRDN): + rdn1 = RDN_class((self.attr1, self.value1)) + rdn2 = RDN_class((self.attr2, self.value2)) + + # in-place addtion + + # Note: If __iadd__ is not available Python will emulate += by + # replacing the lhs object with the result of __add__ (if available). + # Thus += works for both immutable and mutable RDN,DN object, the only + # difference is an immutable without __iadd__ will have a different object + # on the lhs after the operator evaluates. + + rdn1 += rdn2 + self.assertEqual(rdn1, self.rdn3) + self.assertExpectedClass(RDN_class, rdn1, 'self') + for i in range(0, len(rdn1)): + self.assertExpectedClass(RDN_class, rdn1[i], 'AVA') + + rdn1 = RDN_class((self.attr1, self.value1)) + rdn1 += self.ava2 + self.assertEqual(rdn1, self.rdn3) + self.assertExpectedClass(RDN_class, rdn1, 'self') + for i in range(0, len(rdn1)): + self.assertExpectedClass(RDN_class, rdn1[i], 'AVA') + + rdn1 = RDN_class((self.attr1, self.value1)) + rdn1 += self.str_ava2 + self.assertEqual(rdn1, self.rdn3) + self.assertExpectedClass(RDN_class, rdn1, 'self') + for i in range(0, len(rdn1)): + self.assertExpectedClass(RDN_class, rdn1[i], 'AVA') + + # concatenation + rdn1 = RDN_class((self.attr1, self.value1)) + rdn3 = rdn1 + rdn2 + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + + rdn3 = rdn1 + self.ava2 + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + + rdn3 = rdn1 + self.str_ava2 + self.assertEqual(rdn3, self.rdn3) + self.assertExpectedClass(RDN_class, rdn3, 'self') + for i in range(0, len(rdn3)): + self.assertExpectedClass(RDN_class, rdn3[i], 'AVA') + + + def test_hashing(self): + # create RDN's that have the same value + immutable_rdn1 = RDN((self.attr1, self.value1)) + immutable_rdn2 = RDN((self.attr1, self.value1)) + + mutable_rdn1 = EditableRDN((self.attr1, self.value1)) + mutable_rdn2 = EditableRDN((self.attr1, self.value1)) + + # Immutable RDN's that are equal should hash to the same value. + # Mutable RDN's should not be hashable. + + self.assertEqual(immutable_rdn1, immutable_rdn2) + self.assertEqual(immutable_rdn1, mutable_rdn1) + self.assertEqual(immutable_rdn1, mutable_rdn2) + self.assertEqual(mutable_rdn1, immutable_rdn2) + + # Good, everyone's equal, now verify their hash values + + self.assertEqual(hash(immutable_rdn1), hash(immutable_rdn2)) + with self.assertRaises(TypeError): + hash(mutable_rdn1) + with self.assertRaises(TypeError): + hash(mutable_rdn2) + + def test_coerce(self): + # Coerce an immutable to a mutable + immutable_rdn3 = RDN(self.rdn3) + mutable_rdn3 = EditableRDN(immutable_rdn3) + self.assertEqual(mutable_rdn3, self.rdn3) + self.assertEqual(mutable_rdn3, immutable_rdn3) + + # Coerce a mutable to an immutable + mutable_rdn3 = EditableRDN(self.rdn3) + immutable_rdn3 = RDN(mutable_rdn3) + self.assertEqual(immutable_rdn3, self.rdn3) + self.assertEqual(immutable_rdn3, mutable_rdn3) + +class TestDN(unittest.TestCase): + def setUp(self): + # ava1 must sort before ava2 + self.attr1 = 'cn' + self.value1 = 'Bob' + self.str_ava1 = '%s=%s' % (self.attr1, self.value1) + self.ava1 = AVA(self.attr1, self.value1) + + self.str_rdn1 = '%s=%s' % (self.attr1, self.value1) + self.rdn1 = RDN((self.attr1, self.value1)) + + self.attr2 = 'ou' + self.value2 = 'people' + self.str_ava2 = '%s=%s' % (self.attr2, self.value2) + self.ava2 = AVA(self.attr2, self.value2) + + self.str_rdn2 = '%s=%s' % (self.attr2, self.value2) + self.rdn2 = RDN((self.attr2, self.value2)) + + self.str_dn1 = self.str_rdn1 + self.dn1 = DN(self.rdn1) + + self.str_dn2 = self.str_rdn2 + self.dn2 = DN(self.rdn2) + + self.str_dn3 = '%s,%s' % (self.str_rdn1, self.str_rdn2) + self.dn3 = DN(self.rdn1, self.rdn2) + + self.base_rdn1 = RDN(('dc', 'redhat')) + self.base_rdn2 = RDN(('dc', 'com')) + self.base_dn = DN(self.base_rdn1, self.base_rdn2) + + self.container_rdn1 = RDN(('cn', 'sudorules')) + self.container_rdn2 = RDN(('cn', 'sudo')) + self.container_dn = DN(self.container_rdn1, self.container_rdn2) + + self.base_container_dn = DN((self.attr1, self.value1), + self.container_dn, self.base_dn) + + + def assertExpectedClass(self, klass, obj, component): + self.assertIs(obj.__class__, expected_class(klass, component)) + + def test_create(self): + for DN_class in (DN, EditableDN): + # Create with single attr,value pair + dn1 = DN_class((self.attr1, self.value1)) + self.assertEqual(len(dn1), 1) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[0].attr, unicode) + self.assertIsInstance(dn1[0].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + + # Create with single attr,value pair passed as a tuple + dn1 = DN_class((self.attr1, self.value1)) + self.assertEqual(len(dn1), 1) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + + # Creation with multiple attr,value string pairs should fail + with self.assertRaises(ValueError): + dn1 = DN_class(self.attr1, self.value1, self.attr2, self.value2) + + # Create with multiple attr,value pairs passed as tuples & lists + dn1 = DN_class((self.attr1, self.value1), [self.attr2, self.value2]) + self.assertEqual(len(dn1), 2) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + self.assertEqual(dn1[1], self.rdn2) + + # Create with multiple attr,value pairs passed as tuple and RDN + dn1 = DN_class((self.attr1, self.value1), RDN((self.attr2, self.value2))) + self.assertEqual(len(dn1), 2) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + self.assertEqual(dn1[1], self.rdn2) + + # Create with multiple attr,value pairs but reverse + # constructor parameter ordering. RDN ordering should also be + # reversed because DN's are a ordered sequence of RDN's + dn1 = DN_class((self.attr2, self.value2), (self.attr1, self.value1)) + self.assertEqual(len(dn1), 2) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn2) + self.assertEqual(dn1[1], self.rdn1) + + # Create with single RDN object + dn1 = DN_class(self.rdn1) + self.assertEqual(len(dn1), 1) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + + # Create with multiple RDN objects, assure ordering is preserved. + dn1 = DN_class(self.rdn1, self.rdn2) + self.assertEqual(len(dn1), 2) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + self.assertEqual(dn1[1], self.rdn2) + + # Create with multiple RDN objects in different order, assure + # ordering is preserved. + dn1 = DN_class(self.rdn2, self.rdn1) + self.assertEqual(len(dn1), 2) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn2) + self.assertEqual(dn1[1], self.rdn1) + + # Create with single string with 1 RDN + dn1 = DN_class(self.str_rdn1) + self.assertEqual(len(dn1), 1) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + + # Create with single string with 2 RDN's + dn1 = DN_class(self.str_dn3) + self.assertEqual(len(dn1), 2) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + self.assertIsInstance(dn1[i].attr, unicode) + self.assertIsInstance(dn1[i].value, unicode) + self.assertEqual(dn1[0], self.rdn1) + self.assertEqual(dn1[1], self.rdn2) + + # Create with RDN, and 2 DN's (e.g. attr + container + base) + dn1 = DN_class((self.attr1, self.value1), self.container_dn, self.base_dn) + self.assertEqual(len(dn1), 5) + dn_str = ','.join([str(self.rdn1), + str(self.container_rdn1), str(self.container_rdn2), + str(self.base_rdn1), str(self.base_rdn2)]) + self.assertEqual(str(dn1), dn_str) + + def test_str(self): + for DN_class in (DN, EditableDN): + dn1 = DN_class(self.dn1) + dn2 = DN_class(self.dn2) + dn3 = DN_class(self.dn3) + + self.assertEqual(str(dn1), self.str_dn1) + self.assertIsInstance(str(dn1), str) + + self.assertEqual(str(dn2), self.str_dn2) + self.assertIsInstance(str(dn2), str) + + self.assertEqual(str(dn3), self.str_dn3) + self.assertIsInstance(str(dn3), str) + + def test_cmp(self): + for DN_class in (DN, EditableDN): + # Equality + dn1 = DN_class((self.attr1, self.value1)) + + self.assertTrue(dn1 == self.dn1) + self.assertFalse(dn1 != self.dn1) + + self.assertTrue(dn1 == self.str_dn1) + self.assertFalse(dn1 != self.str_dn1) + + result = cmp(dn1, self.dn1) + self.assertEqual(result, 0) + + # Make dn1's attr greater + if DN_class.is_mutable: + dn1[0].attr = self.attr1 + "1" + else: + with self.assertRaises(AttributeError): + dn1[0].attr = self.attr1 + "1" + dn1 = DN_class((self.attr1 + "1", self.value1)) + + self.assertFalse(dn1 == self.dn1) + self.assertTrue(dn1 != self.dn1) + + result = cmp(dn1, self.dn1) + self.assertEqual(result, 1) + + result = cmp(self.dn1, dn1) + self.assertEqual(result, -1) + + # Reset dn1's attr, should be equal again + if DN_class.is_mutable: + dn1[0].attr = self.attr1 + else: + with self.assertRaises(AttributeError): + dn1[0].attr = self.attr1 + dn1 = DN_class((self.attr1, self.value1)) + + result = cmp(dn1, self.dn1) + self.assertEqual(result, 0) + + # Make dn1's value greater + # attr will be equal, this tests secondary comparision component + if DN_class.is_mutable: + dn1[0].value = self.value1 + "1" + else: + with self.assertRaises(AttributeError): + dn1[0].value = self.value1 + "1" + dn1 = DN_class((self.attr1, self.value1 + "1")) + + result = cmp(dn1, self.dn1) + self.assertEqual(result, 1) + + result = cmp(self.dn1, dn1) + self.assertEqual(result, -1) + + # Make sure dn's with more rdn's are greater + result = cmp(self.dn1, self.dn3) + self.assertEqual(result, -1) + result = cmp(self.dn3, self.dn1) + self.assertEqual(result, 1) + + + # Test startswith, endswith + container_dn = DN_class(self.container_dn) + base_container_dn = DN_class(self.base_container_dn) + + self.assertTrue(base_container_dn.startswith(self.rdn1)) + self.assertTrue(base_container_dn.startswith(self.dn1)) + self.assertTrue(base_container_dn.startswith(self.dn1 + container_dn)) + self.assertFalse(base_container_dn.startswith(self.dn2)) + self.assertFalse(base_container_dn.startswith(self.rdn2)) + self.assertTrue(base_container_dn.startswith((self.dn1))) + self.assertTrue(base_container_dn.startswith((self.rdn1))) + self.assertFalse(base_container_dn.startswith((self.rdn2))) + self.assertTrue(base_container_dn.startswith((self.rdn2, self.rdn1))) + self.assertTrue(base_container_dn.startswith((self.dn1, self.dn2))) + + self.assertTrue(base_container_dn.endswith(self.base_dn)) + self.assertTrue(base_container_dn.endswith(container_dn + self.base_dn)) + self.assertFalse(base_container_dn.endswith(DN(self.base_rdn1))) + self.assertTrue(base_container_dn.endswith(DN(self.base_rdn2))) + self.assertTrue(base_container_dn.endswith((DN(self.base_rdn1), DN(self.base_rdn2)))) + + # Test "in" membership + self.assertTrue(self.container_rdn1 in container_dn) + self.assertTrue(container_dn in container_dn) + self.assertFalse(self.base_rdn1 in container_dn) + + self.assertTrue(self.container_rdn1 in base_container_dn) + self.assertTrue(container_dn in base_container_dn) + self.assertTrue(container_dn + self.base_dn in + base_container_dn) + self.assertTrue(self.dn1 + container_dn + self.base_dn in + base_container_dn) + self.assertTrue(self.dn1 + container_dn + self.base_dn == + base_container_dn) + + self.assertFalse(self.container_rdn1 in self.base_dn) + + def test_indexing(self): + for DN_class in (DN, EditableDN): + dn1 = DN_class(self.dn1) + dn2 = DN_class(self.dn2) + dn3 = DN_class(self.dn3) + + self.assertEqual(dn1[0], self.rdn1) + self.assertEqual(dn1[self.rdn1.attr], self.rdn1.value) + with self.assertRaises(KeyError): + dn1['foo'] + + self.assertEqual(dn2[0], self.rdn2) + self.assertEqual(dn2[self.rdn2.attr], self.rdn2.value) + with self.assertRaises(KeyError): + dn2['foo'] + + self.assertEqual(dn3[0], self.rdn1) + self.assertEqual(dn3[self.rdn1.attr], self.rdn1.value) + self.assertEqual(dn3[1], self.rdn2) + self.assertEqual(dn3[self.rdn2.attr], self.rdn2.value) + with self.assertRaises(KeyError): + dn3['foo'] + + with self.assertRaises(TypeError): + dn3[1.0] + + def test_assignments(self): + for DN_class in (DN, EditableDN): + dn_low = 0 + dn_high = 6 + + rdn_args = make_rdn_args(dn_low, dn_high, 'tuple', + default_rdn_attr_arg, default_rdn_value_arg) + dn1 = DN_class(*rdn_args) + + rdn_args = make_rdn_args(dn_low, dn_high, 'list', + default_rdn_attr_arg, default_rdn_value_arg) + dn2 = DN_class(*rdn_args) + + rdn_args = make_rdn_args(dn_low, dn_high, 'RDN', + default_rdn_attr_arg, default_rdn_value_arg) + dn3 = DN_class(*rdn_args) + + self.assertEqual(dn1, dn2) + self.assertEqual(dn1, dn3) + + for i in range(dn_low, dn_high): + attr = default_rdn_attr_arg(i) + value = default_rdn_value_arg(i) + + self.assertEqual(dn1[i].attr, attr) + self.assertEqual(dn1[i].value, value) + self.assertEqual(dn1[attr], value) + self.assertExpectedClass(DN_class, dn1, 'self') + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + + self.assertEqual(dn2[i].attr, attr) + self.assertEqual(dn2[i].value, value) + self.assertEqual(dn2[attr], value) + self.assertExpectedClass(DN_class, dn2, 'self') + self.assertExpectedClass(DN_class, dn2[i], 'RDN') + for j in range(0, len(dn2[i])): + self.assertExpectedClass(DN_class, dn2[i][j], 'AVA') + + self.assertEqual(dn3[i].attr, attr) + self.assertEqual(dn3[i].value, value) + self.assertEqual(dn3[attr], value) + self.assertExpectedClass(DN_class, dn3, 'self') + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + for j in range(0, len(dn3[i])): + self.assertExpectedClass(DN_class, dn3[i][j], 'AVA') + + + for i in range(dn_low, dn_high): + if i % 2: + orig_attr = default_rdn_attr_arg(i) + attr = alt_rdn_attr_arg(i) + value = alt_rdn_value_arg(i) + + if DN_class.is_mutable: + dn1[i] = attr, value + else: + with self.assertRaises(TypeError): + dn1[i] = attr, value + + if DN_class.is_mutable: + dn2[orig_attr] = (attr, value) + else: + with self.assertRaises(TypeError): + dn2[orig_attr] = (attr, value) + + if DN_class.is_mutable: + dn3[i] = RDN((attr, value)) + else: + with self.assertRaises(TypeError): + dn3[i] = RDN((attr, value)) + + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + + self.assertExpectedClass(DN_class, dn2, 'self') + for i in range(0, len(dn2)): + self.assertExpectedClass(DN_class, dn2[i], 'RDN') + for j in range(0, len(dn2[i])): + self.assertExpectedClass(DN_class, dn2[i][j], 'AVA') + + self.assertExpectedClass(DN_class, dn3, 'self') + for i in range(0, len(dn3)): + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + for j in range(0, len(dn3[i])): + self.assertExpectedClass(DN_class, dn3[i][j], 'AVA') + + + if DN_class.is_mutable: + self.assertEqual(dn1, dn2) + self.assertEqual(dn1, dn3) + + for i in range(dn_low, dn_high): + if i % 2: + attr = alt_rdn_attr_arg(i) + value = alt_rdn_value_arg(i) + else: + attr = default_rdn_attr_arg(i) + value = default_rdn_value_arg(i) + self.assertEqual(dn1[i].attr, attr) + self.assertEqual(dn1[i].value, value) + self.assertEqual(dn1[attr], value) + + # Slices + slice_low = 2 + slice_high = 4 + slice_interval = range(slice_low, slice_high) + + # Slices + # Assign via tuple + rdn_args = make_rdn_args(dn_low, dn_high, 'tuple', + default_rdn_attr_arg, default_rdn_value_arg) + dn = DN_class(*rdn_args) + + dn_slice = make_rdn_args(slice_low, slice_high, 'tuple', + alt_rdn_attr_arg, alt_rdn_value_arg) + + if DN_class.is_mutable: + dn[slice_low:slice_high] = dn_slice + for i in range(dn_low, dn_high): + if i in slice_interval: + attr = alt_rdn_attr_arg(i) + value = alt_rdn_value_arg(i) + else: + attr = default_rdn_attr_arg(i) + value = default_rdn_value_arg(i) + self.assertEqual(dn[i].attr, attr) + self.assertEqual(dn[i].value, value) + self.assertEqual(dn[attr], value) + + query_slice = dn[slice_low:slice_high] + for i, query_rdn in enumerate(query_slice): + slice_rdn = RDN(dn_slice[i]) + self.assertEqual(slice_rdn, query_rdn) + else: + with self.assertRaises(TypeError): + dn[slice_low:slice_high] = dn_slice + + + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + # insert + dn = DN_class(self.rdn2) + + if DN_class.is_mutable: + dn.insert(0, self.rdn1) + self.assertEqual(dn, self.dn3) + else: + with self.assertRaises(AttributeError): + dn.insert(0, self.rdn1) + + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + dn = DN_class(self.rdn1) + + if DN_class.is_mutable: + dn.insert(1, (self.attr2, self.value2)) + self.assertEqual(dn, self.dn3) + else: + with self.assertRaises(AttributeError): + dn.insert(1, (self.attr2, self.value2)) + + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + # Slices + # Assign via RDN + rdn_args = make_rdn_args(dn_low, dn_high, 'tuple', + default_rdn_attr_arg, default_rdn_value_arg) + dn = DN_class(*rdn_args) + + dn_slice = make_rdn_args(slice_low, slice_high, 'RDN', + alt_rdn_attr_arg, alt_rdn_value_arg) + + if DN_class.is_mutable: + dn[slice_low:slice_high] = dn_slice + for i in range(dn_low, dn_high): + if i in slice_interval: + attr = alt_rdn_attr_arg(i) + value = alt_rdn_value_arg(i) + else: + attr = default_rdn_attr_arg(i) + value = default_rdn_value_arg(i) + self.assertEqual(dn[i].value, value) + self.assertEqual(dn[attr], value) + + query_slice = dn[slice_low:slice_high] + for i, query_rdn in enumerate(query_slice): + slice_rdn = dn_slice[i] + self.assertEqual(slice_rdn, query_rdn) + else: + with self.assertRaises(TypeError): + dn[slice_low:slice_high] = dn_slice + + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + def test_iter(self): + for DN_class in (DN, EditableDN): + dn1 = DN_class(self.dn1) + dn2 = DN_class(self.dn2) + dn3 = DN_class(self.dn3) + + self.assertEqual(len(dn1), 1) + self.assertEqual(dn1[:], [self.rdn1]) + for i, ava in enumerate(dn1): + if i == 0: + self.assertEqual(ava, self.rdn1) + else: + self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn1))) + + self.assertEqual(len(dn2), 1) + self.assertEqual(dn2[:], [self.rdn2]) + for i, ava in enumerate(dn2): + if i == 0: + self.assertEqual(ava, self.rdn2) + else: + self.fail("got iteration index %d, but len=%d" % (i, len(self.rdn2))) + + self.assertEqual(len(dn3), 2) + self.assertEqual(dn3[:], [self.rdn1, self.rdn2]) + for i, ava in enumerate(dn3): + if i == 0: + self.assertEqual(ava, self.rdn1) + elif i == 1: + self.assertEqual(ava, self.rdn2) + else: + self.fail("got iteration index %d, but len=%d" % (i, len(dn3))) + + + def test_concat(self): + for DN_class in (DN, EditableDN): + dn1 = DN_class((self.attr1, self.value1)) + dn2 = DN_class([self.attr2, self.value2]) + + # in-place addtion + + # Note: If __iadd__ is not available Python will emulate += by + # replacing the lhs object with the result of __add__ (if available). + # Thus += works for both immutable and mutable RDN,DN object, the only + # difference is an immutable without __iadd__ will have a different object + # on the lhs after the operator evaluates. + + dn1 += dn2 + self.assertEqual(dn1, self.dn3) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + + + dn1 = DN_class((self.attr1, self.value1)) + dn1 += self.rdn2 + self.assertEqual(dn1, self.dn3) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + + + dn1 = DN_class((self.attr1, self.value1)) + dn1 += self.dn2 + self.assertEqual(dn1, self.dn3) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + + + dn1 = DN_class((self.attr1, self.value1)) + dn1 += self.str_dn2 + self.assertEqual(dn1, self.dn3) + self.assertExpectedClass(DN_class, dn1, 'self') + for i in range(0, len(dn1)): + self.assertExpectedClass(DN_class, dn1[i], 'RDN') + for j in range(0, len(dn1[i])): + self.assertExpectedClass(DN_class, dn1[i][j], 'AVA') + + + # concatenation + dn1 = DN_class((self.attr1, self.value1)) + dn3 = dn1 + dn2 + self.assertEqual(dn3, self.dn3) + self.assertExpectedClass(DN_class, dn3, 'self') + for i in range(0, len(dn3)): + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + for j in range(0, len(dn3[i])): + self.assertExpectedClass(DN_class, dn3[i][j], 'AVA') + + + dn1 = DN_class((self.attr1, self.value1)) + dn3 = dn1 + self.rdn2 + self.assertEqual(dn3, self.dn3) + self.assertExpectedClass(DN_class, dn3, 'self') + for i in range(0, len(dn3)): + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + for j in range(0, len(dn3[i])): + self.assertExpectedClass(DN_class, dn3[i][j], 'AVA') + + dn3 = dn1 + self.str_rdn2 + self.assertEqual(dn3, self.dn3) + self.assertExpectedClass(DN_class, dn3, 'self') + for i in range(0, len(dn3)): + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + self.assertExpectedClass(DN_class, dn3[i][0], 'AVA') + + dn3 = dn1 + self.str_dn2 + self.assertEqual(dn3, self.dn3) + self.assertExpectedClass(DN_class, dn3, 'self') + self.assertExpectedClass(DN_class, dn3, 'self') + for i in range(0, len(dn3)): + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + for j in range(0, len(dn3[i])): + self.assertExpectedClass(DN_class, dn3[i][j], 'AVA') + + dn3 = dn1 + self.dn2 + self.assertEqual(dn3, self.dn3) + self.assertExpectedClass(DN_class, dn3, 'self') + self.assertExpectedClass(DN_class, dn3, 'self') + for i in range(0, len(dn3)): + self.assertExpectedClass(DN_class, dn3[i], 'RDN') + for j in range(0, len(dn3[i])): + self.assertExpectedClass(DN_class, dn3[i][j], 'AVA') + + def test_find(self): + for DN_class in (DN, EditableDN): + # -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 + dn = DN_class('t=0,t=1,cn=bob,t=3,t=4,t=5,cn=bob,t=7,t=8,t=9') + pat = DN_class('cn=bob') + + # forward + self.assertEqual(dn.find(pat), 2) + self.assertEqual(dn.find(pat, 1), 2) + self.assertEqual(dn.find(pat, 1, 3), 2) + self.assertEqual(dn.find(pat, 2, 3), 2) + self.assertEqual(dn.find(pat, 6), 6) + + self.assertEqual(dn.find(pat, 7), -1) + self.assertEqual(dn.find(pat, 1, 2), -1) + + with self.assertRaises(ValueError): + self.assertEqual(dn.index(pat, 7), -1) + with self.assertRaises(ValueError): + self.assertEqual(dn.index(pat, 1, 2), -1) + + # reverse + self.assertEqual(dn.rfind(pat), 6) + self.assertEqual(dn.rfind(pat, -4), 6) + self.assertEqual(dn.rfind(pat, 6), 6) + self.assertEqual(dn.rfind(pat, 6, 8), 6) + self.assertEqual(dn.rfind(pat, 6, 8), 6) + self.assertEqual(dn.rfind(pat, -8), 6) + self.assertEqual(dn.rfind(pat, -8, -4), 6) + self.assertEqual(dn.rfind(pat, -8, -5), 2) + + self.assertEqual(dn.rfind(pat, 7), -1) + self.assertEqual(dn.rfind(pat, -3), -1) + + with self.assertRaises(ValueError): + self.assertEqual(dn.rindex(pat, 7), -1) + with self.assertRaises(ValueError): + self.assertEqual(dn.rindex(pat, -3), -1) + + + def test_replace(self): + for DN_class in (DN, EditableDN): + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=8,t=9') + pat = DN('cn=bob') + replacement = DN('cn=bob') + + if DN_class.is_mutable: + n_replaced = dn.replace(pat, replacement) + self.assertEqual(n_replaced, 0) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + pat = DN('t=2') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,cn=bob,t=3,t=4,t=5,t=6,t=7,t=8,t=9') + n_replaced = dn.replace(pat, replacement) + self.assertEqual(n_replaced, 1) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,cn=bob,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + n_replaced = dn.replace(pat, replacement, 1) + self.assertEqual(n_replaced, 1) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement, 1) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,cn=bob,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + n_replaced = dn.replace(pat, replacement, 1) + self.assertEqual(n_replaced, 1) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement, 1) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + replacement = DN('cn=bob,ou=people') + + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,cn=bob,ou=people,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + n_replaced = dn.replace(pat, replacement, 1) + self.assertEqual(n_replaced, 1) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement, 1) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=2,t=9') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,cn=bob,ou=people,t=3,t=4,t=5,t=6,t=7,cn=bob,ou=people,t=9') + n_replaced = dn.replace(pat, replacement) + self.assertEqual(n_replaced, 2) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + pat = DN('t=3,t=4') + replacement = DN('cn=bob') + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=8,t=9') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,t=2,cn=bob,t=5,t=6,t=7,t=8,t=9') + n_replaced = dn.replace(pat, replacement) + self.assertEqual(n_replaced, 1) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + pat = DN('t=3,t=4') + replacement = DN('cn=bob,ou=people') + dn = DN_class('t=0,t=1,t=2,t=3,t=4,t=5,t=6,t=7,t=8,t=9') + if DN_class.is_mutable: + expected_dn = DN('t=0,t=1,t=2,cn=bob,ou=people,t=5,t=6,t=7,t=8,t=9') + n_replaced = dn.replace(pat, replacement) + self.assertEqual(n_replaced, 1) + self.assertEqual(dn, expected_dn) + else: + with self.assertRaises(AttributeError): + n_replaced = dn.replace(pat, replacement) + self.assertExpectedClass(DN_class, dn, 'self') + for i in range(0, len(dn)): + self.assertExpectedClass(DN_class, dn[i], 'RDN') + for j in range(0, len(dn[i])): + self.assertExpectedClass(DN_class, dn[i][j], 'AVA') + + def test_hashing(self): + # create DN's that have the same value + immutable_dn1 = DN((self.attr1, self.value1)) + immutable_dn2 = DN((self.attr1, self.value1)) + + mutable_dn1 = EditableDN((self.attr1, self.value1)) + mutable_dn2 = EditableDN((self.attr1, self.value1)) + + # Immutable DN's that are equal should hash to the same value. + # Mutable DN's should not be hashable. + + self.assertEqual(immutable_dn1, immutable_dn2) + self.assertEqual(immutable_dn1, mutable_dn1) + self.assertEqual(immutable_dn1, mutable_dn2) + self.assertEqual(mutable_dn1, immutable_dn2) + + # Good, everyone's equal, now verify their hash values + + self.assertEqual(hash(immutable_dn1), hash(immutable_dn2)) + with self.assertRaises(TypeError): + hash(mutable_dn1) + with self.assertRaises(TypeError): + hash(mutable_dn2) + + # Different immutable DN objects with the same value should + # map to 1 common key and 1 member in a set. The key and + # member are based on the object's value. + # + # Mutable DN objects should be unhashable. + + for DN_class in (DN, EditableDN): + dn1_a = DN_class(self.dn1) + dn1_b = DN_class(self.dn1) + + dn2_a = DN_class(self.dn2) + dn2_b = DN_class(self.dn2) + + dn3_a = DN_class(self.dn3) + dn3_b = DN_class(self.dn3) + + self.assertEqual(dn1_a, dn1_b) + self.assertEqual(dn2_a, dn2_b) + self.assertEqual(dn3_a, dn3_b) + + d = dict() + s = set() + + if DN_class.is_mutable: + with self.assertRaises(TypeError): + d[dn1_a] = str(dn1_a) + with self.assertRaises(TypeError): + d[dn1_b] = str(dn1_b) + with self.assertRaises(TypeError): + d[dn2_a] = str(dn2_a) + with self.assertRaises(TypeError): + d[dn2_b] = str(dn2_b) + + with self.assertRaises(TypeError): + s.add(dn1_a) + with self.assertRaises(TypeError): + s.add(dn1_b) + with self.assertRaises(TypeError): + s.add(dn2_a) + with self.assertRaises(TypeError): + s.add(dn2_b) + else: + d[dn1_a] = str(dn1_a) + d[dn1_b] = str(dn1_b) + d[dn2_a] = str(dn2_a) + d[dn2_b] = str(dn2_b) + + s.add(dn1_a) + s.add(dn1_b) + s.add(dn2_a) + s.add(dn2_b) + + self.assertEqual(len(d), 2) + self.assertEqual(len(s), 2) + self.assertEqual(sorted(d.keys()), sorted([dn1_a, dn2_a])) + self.assertEqual(sorted(s), sorted([dn1_a, dn2_a])) + + self.assertTrue(dn1_a in d) + self.assertTrue(dn1_b in d) + self.assertTrue(dn2_a in d) + self.assertTrue(dn2_b in d) + self.assertFalse(dn3_a in d) + self.assertFalse(dn3_b in d) + + self.assertTrue(d.has_key(dn1_a)) + self.assertTrue(d.has_key(dn1_b)) + self.assertTrue(d.has_key(dn2_a)) + self.assertTrue(d.has_key(dn2_b)) + self.assertFalse(d.has_key(dn3_a)) + self.assertFalse(d.has_key(dn3_b)) + + self.assertTrue(dn1_a in s) + self.assertTrue(dn1_b in s) + self.assertTrue(dn2_a in s) + self.assertTrue(dn2_b in s) + self.assertFalse(dn3_a in s) + self.assertFalse(dn3_b in s) + + def test_coerce(self): + # Coerce an immutable to a mutable + immutable_dn3 = DN(self.dn3) + mutable_dn3 = EditableDN(immutable_dn3) + self.assertEqual(mutable_dn3, self.dn3) + self.assertEqual(mutable_dn3, immutable_dn3) + + # Coerce a mutable to an immutable + mutable_dn3 = EditableDN(self.dn3) + immutable_dn3 = DN(mutable_dn3) + self.assertEqual(immutable_dn3, self.dn3) + self.assertEqual(immutable_dn3, mutable_dn3) + +class TestEscapes(unittest.TestCase): + def setUp(self): + self.privilege = 'R,W privilege' + self.dn_str_hex_escape = 'cn=R\\2cW privilege,cn=privileges,cn=pbac,dc=idm,dc=lab,dc=bos,dc=redhat,dc=com' + self.dn_str_backslash_escape = 'cn=R\\,W privilege,cn=privileges,cn=pbac,dc=idm,dc=lab,dc=bos,dc=redhat,dc=com' + + def test_escape(self): + for DN_class in (DN, EditableDN): + dn = DN_class(self.dn_str_hex_escape) + self.assertEqual(dn['cn'], self.privilege) + self.assertEqual(dn[0].value, self.privilege) + + dn = DN_class(self.dn_str_backslash_escape) + self.assertEqual(dn['cn'], self.privilege) + self.assertEqual(dn[0].value, self.privilege) + +class TestInternationalization(unittest.TestCase): + def setUp(self): + # Hello in Arabic + self.arabic_hello_utf8 = '\xd9\x85\xd9\x83\xd9\x8a\xd9\x84' + \ + '\xd8\xb9\x20\xd9\x85\xd8\xa7\xd9' + \ + '\x84\xd9\x91\xd8\xb3\xd9\x84\xd8\xa7' + + self.arabic_hello_unicode = self.arabic_hello_utf8.decode('utf-8') + + def test_i18n(self): + self.assertEqual(self.arabic_hello_utf8, + self.arabic_hello_unicode.encode('utf-8')) + + # AVA's + # test attr i18n + for AVA_class in (AVA, EditableAVA): + ava1 = AVA_class(self.arabic_hello_unicode, 'foo') + self.assertIsInstance(ava1.attr, unicode) + self.assertIsInstance(ava1.value, unicode) + self.assertEqual(ava1.attr, self.arabic_hello_unicode) + self.assertEqual(str(ava1), self.arabic_hello_utf8+'=foo') + + ava1 = AVA_class(self.arabic_hello_utf8, 'foo') + self.assertIsInstance(ava1.attr, unicode) + self.assertIsInstance(ava1.value, unicode) + self.assertEqual(ava1.attr, self.arabic_hello_unicode) + self.assertEqual(str(ava1), self.arabic_hello_utf8+'=foo') + + # test value i18n + ava1 = AVA_class('cn', self.arabic_hello_unicode) + self.assertIsInstance(ava1.attr, unicode) + self.assertIsInstance(ava1.value, unicode) + self.assertEqual(ava1.value, self.arabic_hello_unicode) + self.assertEqual(str(ava1), 'cn='+self.arabic_hello_utf8) + + ava1 = AVA_class('cn', self.arabic_hello_utf8) + self.assertIsInstance(ava1.attr, unicode) + self.assertIsInstance(ava1.value, unicode) + self.assertEqual(ava1.value, self.arabic_hello_unicode) + self.assertEqual(str(ava1), 'cn='+self.arabic_hello_utf8) + + # RDN's + # test attr i18n + for RDN_class in (RDN, EditableRDN): + rdn1 = RDN_class((self.arabic_hello_unicode, 'foo')) + self.assertIsInstance(rdn1.attr, unicode) + self.assertIsInstance(rdn1.value, unicode) + self.assertEqual(rdn1.attr, self.arabic_hello_unicode) + self.assertEqual(str(rdn1), self.arabic_hello_utf8+'=foo') + + rdn1 = RDN_class((self.arabic_hello_utf8, 'foo')) + self.assertIsInstance(rdn1.attr, unicode) + self.assertIsInstance(rdn1.value, unicode) + self.assertEqual(rdn1.attr, self.arabic_hello_unicode) + self.assertEqual(str(rdn1), self.arabic_hello_utf8+'=foo') + + # test value i18n + rdn1 = RDN_class(('cn', self.arabic_hello_unicode)) + self.assertIsInstance(rdn1.attr, unicode) + self.assertIsInstance(rdn1.value, unicode) + self.assertEqual(rdn1.value, self.arabic_hello_unicode) + self.assertEqual(str(rdn1), 'cn='+self.arabic_hello_utf8) + + rdn1 = RDN_class(('cn', self.arabic_hello_utf8)) + self.assertIsInstance(rdn1.attr, unicode) + self.assertIsInstance(rdn1.value, unicode) + self.assertEqual(rdn1.value, self.arabic_hello_unicode) + self.assertEqual(str(rdn1), 'cn='+self.arabic_hello_utf8) + + # DN's + # test attr i18n + for DN_class in (DN, EditableDN): + dn1 = DN_class((self.arabic_hello_unicode, 'foo')) + self.assertIsInstance(dn1[0].attr, unicode) + self.assertIsInstance(dn1[0].value, unicode) + self.assertEqual(dn1[0].attr, self.arabic_hello_unicode) + self.assertEqual(str(dn1), self.arabic_hello_utf8+'=foo') + + dn1 = DN_class((self.arabic_hello_utf8, 'foo')) + self.assertIsInstance(dn1[0].attr, unicode) + self.assertIsInstance(dn1[0].value, unicode) + self.assertEqual(dn1[0].attr, self.arabic_hello_unicode) + self.assertEqual(str(dn1), self.arabic_hello_utf8+'=foo') + + # test value i18n + dn1 = DN_class(('cn', self.arabic_hello_unicode)) + self.assertIsInstance(dn1[0].attr, unicode) + self.assertIsInstance(dn1[0].value, unicode) + self.assertEqual(dn1[0].value, self.arabic_hello_unicode) + self.assertEqual(str(dn1), 'cn='+self.arabic_hello_utf8) + + dn1 = DN_class(('cn', self.arabic_hello_utf8)) + self.assertIsInstance(dn1[0].attr, unicode) + self.assertIsInstance(dn1[0].value, unicode) + self.assertEqual(dn1[0].value, self.arabic_hello_unicode) + self.assertEqual(str(dn1), 'cn='+self.arabic_hello_utf8) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_ipaserver/test_changepw.py b/tests/test_ipaserver/test_changepw.py index 035766855..4491e0b6e 100644 --- a/tests/test_ipaserver/test_changepw.py +++ b/tests/test_ipaserver/test_changepw.py @@ -23,7 +23,7 @@ from httptest import Unauthorized_HTTP_test from tests.test_xmlrpc.xmlrpc_test import XMLRPC_test from tests.util import assert_equal, assert_not_equal from ipalib import api, errors -from ipalib.dn import DN +from ipapython.dn import DN import ldap testuser = u'tuser' @@ -57,9 +57,7 @@ class test_changepw(XMLRPC_test, Unauthorized_HTTP_test): ) def _checkpw(self, user, password): - dn = str(DN(('uid', user), - api.env.container_user, - api.env.basedn)) + dn = str(DN(('uid', user), api.env.container_user, api.env.basedn)) conn = ldap.initialize(api.env.ldap_uri) try: conn.simple_bind_s(dn, password) diff --git a/tests/test_ipaserver/test_ldap.py b/tests/test_ipaserver/test_ldap.py index 1bbd94fb5..cd3ba3cd5 100644 --- a/tests/test_ipaserver/test_ldap.py +++ b/tests/test_ipaserver/test_ldap.py @@ -33,7 +33,7 @@ from ipalib.plugins.host import host import nss.nss as nss from ipalib import api, x509, create_api, errors from ipapython import ipautil -from ipalib.dn import * +from ipapython.dn import DN class test_ldap(object): """ @@ -45,8 +45,8 @@ class test_ldap(object): self.ldapuri = 'ldap://%s' % ipautil.format_netloc(api.env.host) self.ccache = '/tmp/krb5cc_%d' % os.getuid() nss.nss_init_nodb() - self.dn = str(DN(('krbprincipalname','ldap/%s@%s' % (api.env.host, api.env.realm)), - ('cn','services'),('cn','accounts'),api.env.basedn)) + self.dn = DN(('krbprincipalname','ldap/%s@%s' % (api.env.host, api.env.realm)), + ('cn','services'),('cn','accounts'),api.env.basedn) def tearDown(self): if self.conn and self.conn.isconnected(): @@ -90,7 +90,7 @@ class test_ldap(object): else: raise nose.SkipTest("No directory manager password in %s" % pwfile) self.conn = ldap2(shared_instance=False, ldap_uri=self.ldapuri) - self.conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) + self.conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password) (dn, entry_attrs) = self.conn.get_entry(self.dn, ['usercertificate']) cert = entry_attrs.get('usercertificate') cert = cert[0] @@ -120,7 +120,7 @@ class test_ldap(object): fp.close() else: raise nose.SkipTest("No directory manager password in %s" % pwfile) - myapi.Backend.ldap2.connect(bind_dn="cn=Directory Manager", bind_pw=dm_password) + myapi.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password) result = myapi.Command['service_show']('ldap/%s@%s' % (api.env.host, api.env.realm,)) entry_attrs = result['result'] @@ -145,4 +145,3 @@ class test_ldap(object): cert = cert[0] serial = unicode(x509.get_serial_number(cert, x509.DER)) assert serial is not None - diff --git a/tests/test_xmlrpc/test_attr.py b/tests/test_xmlrpc/test_attr.py index f5003c403..799c77939 100644 --- a/tests/test_xmlrpc/test_attr.py +++ b/tests/test_xmlrpc/test_attr.py @@ -21,29 +21,17 @@ Test --setattr and --addattr and other attribute-specific issues """ -from ipalib import api, errors, x509 +from ipalib import api, errors from tests.test_xmlrpc import objectclasses -from xmlrpc_test import (Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_date, - fuzzy_hex, fuzzy_hash, fuzzy_issuer) -from ipalib.dn import * -import base64 - -user1 = u'tuser1' -fqdn1 = u'testhost1.%s' % api.env.domain - -# We can use the same cert we generated for the service tests -fd = open('tests/test_xmlrpc/service.crt', 'r') -servercert = fd.readlines() -servercert = u''.join(servercert) -servercert = x509.strip_header(servercert) -servercert = servercert.replace('\n', '') -fd.close() +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +user1=u'tuser1' class test_attr(Declarative): cleanup_commands = [ ('user_del', [user1], {}), - ('host_del', [fqdn1], {}), ] tests = [ @@ -72,16 +60,13 @@ class test_attr(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), has_keytab=False, has_password=False, ), @@ -563,71 +548,4 @@ class test_attr(Declarative): desc='Server is unwilling to perform', info=''), ), - dict( - desc='Try to create %r with description and --addattr description' % fqdn1, - command=('host_add', [fqdn1], - dict( - description=u'Test host 1', - addattr=u'description=Test host 2', - force=True, - ), - ), - expected=errors.OnlyOneValueAllowed(attr='description'), - ), - - dict( - desc='Create %r with a certificate' % fqdn1, - command=('host_add', [fqdn1], - dict( - usercertificate=servercert, - force=True, - ), - ), - expected=dict( - value=fqdn1, - summary=u'Added host "%s"' % fqdn1, - result=dict( - dn=lambda x: DN(x) == DN(('fqdn',fqdn1),('cn','computers'), - ('cn','accounts'),api.env.basedn), - fqdn=[fqdn1], - krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], - objectclass=objectclasses.host, - ipauniqueid=[fuzzy_uuid], - managedby_host=[fqdn1], - usercertificate=[base64.b64decode(servercert)], - valid_not_before=fuzzy_date, - valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), - serial_number=fuzzy_digits, - serial_number_hex=fuzzy_hex, - md5_fingerprint=fuzzy_hash, - sha1_fingerprint=fuzzy_hash, - issuer=fuzzy_issuer, - has_keytab=False, - has_password=False, - ), - ), - ), - - dict( - desc='Remove %r certificate using --delattr' % fqdn1, - command=('host_mod', [fqdn1], - dict( - delattr=u'usercertificate=%s' % servercert, - ), - ), - expected=dict( - value=fqdn1, - summary=u'Modified host "%s"' % fqdn1, - result=dict( - fqdn=[fqdn1], - krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], - managedby_host=[fqdn1], - has_keytab=False, - has_password=False, - ), - ), - ), - ] diff --git a/tests/test_xmlrpc/test_automember_plugin.py b/tests/test_xmlrpc/test_automember_plugin.py index ceae8c3ff..36a510939 100644 --- a/tests/test_xmlrpc/test_automember_plugin.py +++ b/tests/test_xmlrpc/test_automember_plugin.py @@ -22,6 +22,7 @@ Test the `ipalib/plugins/automember.py` module. """ from ipalib import api, errors +from ipapython.dn import DN from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid @@ -132,7 +133,7 @@ class test_automember(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=u'cn=%s,cn=groups,cn=accounts,%s' % (group1, api.env.basedn), + dn=DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -151,8 +152,8 @@ class test_automember(Declarative): description=[u'Test desc'], objectclass=objectclasses.hostgroup, ipauniqueid=[fuzzy_uuid], - mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (hostgroup1, api.env.basedn)], - dn=u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn), + mepmanagedentry=[DN(('cn', hostgroup1), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -171,8 +172,8 @@ class test_automember(Declarative): description=[u'Test desc'], objectclass=objectclasses.hostgroup, ipauniqueid=[fuzzy_uuid], - mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (hostgroup2, api.env.basedn)], - dn=u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup2, api.env.basedn), + mepmanagedentry=[DN(('cn', hostgroup2), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -191,8 +192,8 @@ class test_automember(Declarative): description=[u'Test desc'], objectclass=objectclasses.hostgroup, ipauniqueid=[fuzzy_uuid], - mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (hostgroup3, api.env.basedn)], - dn=u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup3, api.env.basedn), + mepmanagedentry=[DN(('cn', hostgroup3), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -211,8 +212,8 @@ class test_automember(Declarative): description=[u'Test desc'], objectclass=objectclasses.hostgroup, ipauniqueid=[fuzzy_uuid], - mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (hostgroup4, api.env.basedn)], - dn=u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup4, api.env.basedn), + mepmanagedentry=[DN(('cn', hostgroup4), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -232,7 +233,7 @@ class test_automember(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=u'cn=%s,cn=groups,cn=accounts,%s' % (defaultgroup1, api.env.basedn), + dn=DN(('cn', defaultgroup1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -251,8 +252,8 @@ class test_automember(Declarative): description=[u'Default test desc'], objectclass=objectclasses.hostgroup, ipauniqueid=[fuzzy_uuid], - mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (defaulthostgroup1, api.env.basedn)], - dn=u'cn=%s,cn=hostgroups,cn=accounts,%s' % (defaulthostgroup1, api.env.basedn), + mepmanagedentry=[DN(('cn', defaulthostgroup1), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), ), ), ), @@ -269,9 +270,9 @@ class test_automember(Declarative): result=dict( cn=[group1], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (group1, api.env.basedn)], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], objectclass=objectclasses.automember, - dn=u'cn=%s,cn=group,cn=automember,cn=etc,%s' % (group1, api.env.basedn), + dn=DN(('cn', group1), ('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ), ), @@ -299,7 +300,7 @@ class test_automember(Declarative): cn=[group1], description=[u'Test desc'], automemberinclusiveregex=[u'manager=%s' % group_include_regex], - automembertargetgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (group1, api.env.basedn)], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], ), ), ), @@ -318,9 +319,9 @@ class test_automember(Declarative): result=dict( cn=[hostgroup1], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], objectclass=objectclasses.automember, - dn=u'cn=%s,cn=hostgroup,cn=automember,cn=etc,%s' % (hostgroup1, api.env.basedn), + dn=DN(('cn', hostgroup1), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ), ), @@ -348,7 +349,7 @@ class test_automember(Declarative): cn=[hostgroup1], description=[u'Test desc'], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], ), ), ), @@ -401,7 +402,7 @@ class test_automember(Declarative): result=dict( cn=[hostgroup1], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, u'fqdn=%s' % hostgroup_include_regex3, u'fqdn=%s' % hostgroup_include_regex2, @@ -428,9 +429,9 @@ class test_automember(Declarative): result=dict( cn=[hostgroup2], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup2, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], objectclass=objectclasses.automember, - dn=u'cn=%s,cn=hostgroup,cn=automember,cn=etc,%s' % (hostgroup2, api.env.basedn), + dn=DN(('cn', hostgroup2), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ), ), @@ -458,7 +459,7 @@ class test_automember(Declarative): cn=[hostgroup2], description=[u'Test desc'], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup2, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], ), ), ), @@ -477,9 +478,9 @@ class test_automember(Declarative): result=dict( cn=[hostgroup3], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup3, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], objectclass=objectclasses.automember, - dn=u'cn=%s,cn=hostgroup,cn=automember,cn=etc,%s' % (hostgroup3, api.env.basedn), + dn=DN(('cn', hostgroup3), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ), ), @@ -507,7 +508,7 @@ class test_automember(Declarative): cn=[hostgroup3], description=[u'Test desc'], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex2], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup3, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], ), ), ), @@ -526,9 +527,9 @@ class test_automember(Declarative): result=dict( cn=[hostgroup4], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup4, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], objectclass=objectclasses.automember, - dn=u'cn=%s,cn=hostgroup,cn=automember,cn=etc,%s' % (hostgroup4, api.env.basedn), + dn=DN(('cn', hostgroup4), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ), ), @@ -556,7 +557,7 @@ class test_automember(Declarative): cn=[hostgroup4], description=[u'Test desc'], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex3], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup4, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], ), ), ), @@ -574,8 +575,8 @@ class test_automember(Declarative): cn=[group1], description=[u'Test desc'], automemberinclusiveregex=[u'manager=%s' % group_include_regex], - automembertargetgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (group1, api.env.basedn)], - dn=u'cn=%s,cn=group,cn=automember,cn=etc,%s' % (group1, api.env.basedn), + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + dn=DN(('cn', group1), ('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), summary=None, ), @@ -596,8 +597,8 @@ class test_automember(Declarative): cn=[group1], description=[u'Test desc'], automemberinclusiveregex=[u'manager=%s' % group_include_regex], - automembertargetgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (group1, api.env.basedn)], - dn=u'cn=%s,cn=group,cn=automember,cn=etc,%s' % (group1, api.env.basedn), + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + dn=DN(('cn', group1), ('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ], summary=u'1 rules matched', @@ -618,7 +619,7 @@ class test_automember(Declarative): cn=[group1], description=[u'New desc 1'], automemberinclusiveregex=[u'manager=%s' % group_include_regex], - automembertargetgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (group1, api.env.basedn)], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], ), summary=u'Modified automember rule "%s"' % group1, value=group1, @@ -637,7 +638,7 @@ class test_automember(Declarative): result=dict( cn=[hostgroup1], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, u'fqdn=%s' % hostgroup_include_regex3, u'fqdn=%s' % hostgroup_include_regex2, @@ -646,7 +647,7 @@ class test_automember(Declarative): u'fqdn=%s' % hostgroup_exclude_regex3, u'fqdn=%s' % hostgroup_exclude_regex, ], - dn=u'cn=%s,cn=hostgroup,cn=automember,cn=etc,%s' % (hostgroup1, api.env.basedn), + dn=DN(('cn', hostgroup1), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), summary=None, ), @@ -666,7 +667,7 @@ class test_automember(Declarative): dict( cn=[hostgroup1], description=[u'Test desc'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, u'fqdn=%s' % hostgroup_include_regex3, u'fqdn=%s' % hostgroup_include_regex2, @@ -675,7 +676,7 @@ class test_automember(Declarative): u'fqdn=%s' % hostgroup_exclude_regex3, u'fqdn=%s' % hostgroup_exclude_regex, ], - dn=u'cn=%s,cn=hostgroup,cn=automember,cn=etc,%s' % (hostgroup1, api.env.basedn), + dn=DN(('cn', hostgroup1), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), ), ], summary=u'1 rules matched', @@ -695,7 +696,7 @@ class test_automember(Declarative): result=dict( cn=[hostgroup1], description=[u'New desc 1'], - automembertargetgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn)], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, u'fqdn=%s' % hostgroup_include_regex3, u'fqdn=%s' % hostgroup_include_regex2, @@ -722,7 +723,7 @@ class test_automember(Declarative): expected=dict( result=dict( cn=[u'Group'], - automemberdefaultgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (defaultgroup1, api.env.basedn)], + automemberdefaultgroup=[DN(('cn', defaultgroup1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], ), value=u'group', summary=u'Set default (fallback) group for automember "group"', @@ -737,9 +738,9 @@ class test_automember(Declarative): ), expected=dict( result=dict( - dn=u'cn=group,cn=automember,cn=etc,%s' % (api.env.basedn), + dn=DN(('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), cn=[u'Group'], - automemberdefaultgroup=[u'cn=%s,cn=groups,cn=accounts,%s' % (defaultgroup1, api.env.basedn)], + automemberdefaultgroup=[DN(('cn', defaultgroup1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], ), value=u'group', summary=None, @@ -758,7 +759,7 @@ class test_automember(Declarative): expected=dict( result=dict( cn=[u'Hostgroup'], - automemberdefaultgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (defaulthostgroup1, api.env.basedn)], + automemberdefaultgroup=[DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], ), value=u'hostgroup', summary=u'Set default (fallback) group for automember "hostgroup"', @@ -775,9 +776,9 @@ class test_automember(Declarative): ), expected=dict( result=dict( - dn=u'cn=hostgroup,cn=automember,cn=etc,%s' % (api.env.basedn), + dn=DN(('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), cn=[u'Hostgroup'], - automemberdefaultgroup=[u'cn=%s,cn=hostgroups,cn=accounts,%s' % (defaulthostgroup1, api.env.basedn)], + automemberdefaultgroup=[DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], ), value=u'hostgroup', summary=None, @@ -810,10 +811,13 @@ class test_automember(Declarative): cn=[u'Michael Scott'], initials=[u'MS'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], - mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (manager1, api.env.basedn)], + krbpwdpolicyreference=[DN(('cn', 'global_policy'), ('cn', api.env.realm), ('cn', 'kerberos'), + api.env.basedn)], + mepmanagedentry=[DN(('cn', manager1), ('cn', 'groups'), ('cn', 'accounts'), + api.env.basedn)], memberof_group=[u'defaultgroup1', u'ipausers'], - dn=u'uid=mscott,cn=users,cn=accounts,' + api.env.basedn, + dn=DN(('uid', 'mscott'), ('cn', 'users'), ('cn', 'accounts'), + api.env.basedn), ), ), ), @@ -840,15 +844,18 @@ class test_automember(Declarative): uid=[user1], uidnumber=[fuzzy_digits], gidnumber=[fuzzy_digits], - manager=[u'uid=mscott,cn=users,cn=accounts,%s' % api.env.basedn], + manager=[DN(('uid', 'mscott'), ('cn', 'users'), ('cn', 'accounts'), api.env.basedn)], displayname=[u'Test User1'], cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], - mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + krbpwdpolicyreference=[DN(('cn', 'global_policy'), ('cn', api.env.realm), ('cn', 'kerberos'), + api.env.basedn)], + mepmanagedentry=[DN(('cn', user1), ('cn', 'groups'), ('cn', 'accounts'), + api.env.basedn)], memberof_group=[u'group1', u'ipausers'], - dn=u'uid=tuser1,cn=users,cn=accounts,' + api.env.basedn, + dn=DN(('uid', 'tuser1'), ('cn', 'users'), ('cn', 'accounts'), + api.env.basedn), ), ), ), @@ -867,7 +874,7 @@ class test_automember(Declarative): value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( - dn=u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn1, api.env.basedn), + dn=DN(('fqdn', fqdn1), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -895,7 +902,7 @@ class test_automember(Declarative): value=fqdn2, summary=u'Added host "%s"' % fqdn2, result=dict( - dn=u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn2, api.env.basedn), + dn=DN(('fqdn', fqdn2), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), fqdn=[fqdn2], description=[u'Test host 2'], l=[u'Undisclosed location 1'], @@ -923,7 +930,7 @@ class test_automember(Declarative): value=fqdn3, summary=u'Added host "%s"' % fqdn3, result=dict( - dn=u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn3, api.env.basedn), + dn=DN(('fqdn', fqdn3), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), fqdn=[fqdn3], description=[u'Test host 3'], l=[u'Undisclosed location 1'], @@ -951,7 +958,7 @@ class test_automember(Declarative): value=fqdn4, summary=u'Added host "%s"' % fqdn4, result=dict( - dn=u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn4, api.env.basedn), + dn=DN(('fqdn', fqdn4), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), fqdn=[fqdn4], description=[u'Test host 4'], l=[u'Undisclosed location 1'], @@ -979,7 +986,7 @@ class test_automember(Declarative): value=fqdn5, summary=u'Added host "%s"' % fqdn5, result=dict( - dn=u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn5, api.env.basedn), + dn=DN(('fqdn', fqdn5), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), fqdn=[fqdn5], description=[u'Test host 5'], l=[u'Undisclosed location 1'], @@ -1001,7 +1008,7 @@ class test_automember(Declarative): value=hostgroup1, summary=None, result={ - 'dn': u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup1, api.env.basedn), + 'dn': DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), 'member_host': [u'%s' % fqdn1], 'cn': [hostgroup1], 'description': [u'Test desc'], @@ -1017,7 +1024,7 @@ class test_automember(Declarative): value=defaulthostgroup1, summary=None, result={ - 'dn': u'cn=%s,cn=hostgroups,cn=accounts,%s' % (defaulthostgroup1, api.env.basedn), + 'dn': DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), 'member_host': [u'%s' % fqdn2], 'cn': [defaulthostgroup1], 'description': [u'Default test desc'], @@ -1033,7 +1040,7 @@ class test_automember(Declarative): value=hostgroup2, summary=None, result={ - 'dn': u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup2, api.env.basedn), + 'dn': DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), 'member_host': [u'%s' % fqdn3], 'cn': [hostgroup2], 'description': [u'Test desc'], @@ -1049,7 +1056,7 @@ class test_automember(Declarative): value=hostgroup3, summary=None, result={ - 'dn': u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup3, api.env.basedn), + 'dn': DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), 'member_host': [u'%s' % fqdn4], 'cn': [hostgroup3], 'description': [u'Test desc'], @@ -1065,7 +1072,7 @@ class test_automember(Declarative): value=hostgroup4, summary=None, result={ - 'dn': u'cn=%s,cn=hostgroups,cn=accounts,%s' % (hostgroup4, api.env.basedn), + 'dn': DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), 'member_host': [u'%s' % fqdn5], 'cn': [hostgroup4], 'description': [u'Test desc'], diff --git a/tests/test_xmlrpc/test_automount_plugin.py b/tests/test_xmlrpc/test_automount_plugin.py index d6c289979..19968a0dc 100644 --- a/tests/test_xmlrpc/test_automount_plugin.py +++ b/tests/test_xmlrpc/test_automount_plugin.py @@ -28,7 +28,7 @@ import shutil from ipalib import api from ipalib import errors -from ipalib.dn import DN +from ipapython.dn import DN from nose.tools import raises, assert_raises # pylint: disable=E0611 from xmlrpc_test import XMLRPC_test, assert_attr_equal @@ -238,40 +238,36 @@ class test_automount(AutomountTest): result=dict( keys={'auto.direct': ()}, orphanmaps=(dict( - dn=lambda dn: DN(dn) == DN( - ('automountmapname', self.mapname), - ('cn', self.locname), - ('cn', 'automount'), api.env.basedn), + dn=DN(('automountmapname', self.mapname), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), description=(u'description of map',), automountmapname=(u'testmap',)),), orphankeys=[( dict( - dn=lambda dn: DN(dn) == DN( - ('description', self.keyname2), - ('automountmapname', 'testmap'), - ('cn', self.locname), - ('cn', 'automount'), api.env.basedn), + dn=DN(('description', self.keyname2), + ('automountmapname', 'testmap'), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), automountkey=(self.keyname2,), description=(self.keyname2,), automountinformation=(u'ro',), ), dict( - dn=lambda dn: DN(dn) == DN( - ('description', self.keyname_rename), - ('automountmapname', 'testmap'), - ('cn', self.locname), - ('cn', 'automount'), api.env.basedn), + dn=DN(('description', self.keyname_rename), + ('automountmapname', 'testmap'), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), automountkey=(self.keyname_rename,), description=(self.keyname_rename,), automountinformation=(u'rw',), ))], maps=( dict( - dn=lambda dn: DN(dn) == DN( - ('description', '/- auto.direct'), - ('automountmapname', 'auto.master'), - ('cn', self.locname), - ('cn', 'automount'), api.env.basedn), + dn=DN(('description', '/- auto.direct'), + ('automountmapname', 'auto.master'), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), automountkey=(u'/-',), description=(u'/- auto.direct',), automountinformation=(u'auto.direct',) diff --git a/tests/test_xmlrpc/test_batch_plugin.py b/tests/test_xmlrpc/test_batch_plugin.py index 19728adda..55e557050 100644 --- a/tests/test_xmlrpc/test_batch_plugin.py +++ b/tests/test_xmlrpc/test_batch_plugin.py @@ -25,7 +25,7 @@ from ipalib import api, errors from tests.test_xmlrpc import objectclasses from tests.util import assert_equal, Fuzzy, assert_deepequal from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import DN +from ipapython.dn import DN group1 = u'testgroup1' @@ -96,11 +96,10 @@ class test_batch(Declarative): objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], gidnumber=[fuzzy_digits], - dn=lambda x: DN(x) == \ - DN(('cn', 'testgroup1'), - ('cn', 'groups'), - ('cn', 'accounts'), - api.env.basedn), + dn=DN(('cn', 'testgroup1'), + ('cn', 'groups'), + ('cn', 'accounts'), + api.env.basedn), ), error=None), dict( @@ -159,11 +158,10 @@ class test_batch(Declarative): objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], gidnumber=[fuzzy_digits], - dn=lambda x: DN(x) == \ - DN(('cn', 'testgroup1'), - ('cn', 'groups'), - ('cn', 'accounts'), - api.env.basedn), + dn=DN(('cn', 'testgroup1'), + ('cn', 'groups'), + ('cn', 'accounts'), + api.env.basedn), ), error=None), ), diff --git a/tests/test_xmlrpc/test_cert.py b/tests/test_xmlrpc/test_cert.py index 90809ab30..4f95ffe19 100644 --- a/tests/test_xmlrpc/test_cert.py +++ b/tests/test_xmlrpc/test_cert.py @@ -33,7 +33,7 @@ import tempfile from ipapython import ipautil import nose import base64 -from ipalib.dn import * +from ipapython.dn import DN # So we can save the cert from issuance and compare it later cert = None diff --git a/tests/test_xmlrpc/test_delegation_plugin.py b/tests/test_xmlrpc/test_delegation_plugin.py index 95aa82877..a31fed3ed 100644 --- a/tests/test_xmlrpc/test_delegation_plugin.py +++ b/tests/test_xmlrpc/test_delegation_plugin.py @@ -24,6 +24,7 @@ Test the `ipalib/plugins/delegation.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN delegation1 = u'testdelegation' member1 = u'admins' @@ -148,8 +149,9 @@ class test_delegation(Declarative): value=delegation1, summary=None, result={ - 'aci': u'(targetattr = "street || c || l || st || postalcode")(targetfilter = "(memberOf=cn=admins,cn=groups,cn=accounts,%s)")(version 3.0;acl "delegation:testdelegation";allow (write) groupdn = "ldap:///cn=editors,cn=groups,cn=accounts,%s";)' \ - % (api.env.basedn, api.env.basedn) + 'aci': u'(targetattr = "street || c || l || st || postalcode")(targetfilter = "(memberOf=%s)")(version 3.0;acl "delegation:testdelegation";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('cn', 'admins'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'editors'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)) }, ), ), @@ -200,8 +202,9 @@ class test_delegation(Declarative): summary=u'1 delegation matched', result=[ { - 'aci': u'(targetattr = "street || c || l || st || postalcode")(targetfilter = "(memberOf=cn=admins,cn=groups,cn=accounts,%s)")(version 3.0;acl "delegation:testdelegation";allow (write) groupdn = "ldap:///cn=editors,cn=groups,cn=accounts,%s";)' \ - % (api.env.basedn, api.env.basedn), + 'aci': u'(targetattr = "street || c || l || st || postalcode")(targetfilter = "(memberOf=%s)")(version 3.0;acl "delegation:testdelegation";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('cn', 'admins'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'editors'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)), }, ], ), diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py index 9c9b22392..2b6d53c0b 100644 --- a/tests/test_xmlrpc/test_dns_plugin.py +++ b/tests/test_xmlrpc/test_dns_plugin.py @@ -22,7 +22,7 @@ Test the `ipalib/plugins/dns.py` module. import nose from ipalib import api, errors -from ipalib.dn import * +from ipapython.dn import DN from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid @@ -139,7 +139,7 @@ class test_dns(Declarative): 'value': dnszone1, 'summary': None, 'result': { - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [dnszone1], 'idnszoneactive': [u'TRUE'], 'idnssoamname': [dnszone1_mname], @@ -200,7 +200,7 @@ class test_dns(Declarative): 'value': dnszone2, 'summary': None, 'result': { - 'dn': unicode(dnszone2_dn), + 'dn': dnszone2_dn, 'idnsname': [dnszone2], 'idnszoneactive': [u'TRUE'], 'idnssoamname': [dnszone2_mname], @@ -240,7 +240,7 @@ class test_dns(Declarative): 'value': dnszone1, 'summary': None, 'result': { - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [dnszone1], 'idnszoneactive': [u'TRUE'], 'nsrecord': [dnszone1_mname], @@ -295,7 +295,7 @@ class test_dns(Declarative): 'value': revdnszone1, 'summary': None, 'result': { - 'dn': unicode(revdnszone1_dn), + 'dn': revdnszone1_dn, 'idnsname': [revdnszone1], 'idnszoneactive': [u'TRUE'], 'idnssoamname': [dnszone1_mname], @@ -325,7 +325,7 @@ class test_dns(Declarative): 'count': 2, 'truncated': False, 'result': [{ - 'dn': unicode(revdnszone1_dn), + 'dn': revdnszone1_dn, 'idnsname': [revdnszone1], 'idnszoneactive': [u'TRUE'], 'nsrecord': [dnszone1_mname], @@ -340,7 +340,7 @@ class test_dns(Declarative): 'idnsallowquery': [u'any;'], }, { - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [dnszone1], 'idnszoneactive': [u'TRUE'], 'nsrecord': [dnszone1_mname], @@ -366,7 +366,7 @@ class test_dns(Declarative): 'count': 1, 'truncated': False, 'result': [{ - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [dnszone1], 'idnszoneactive': [u'TRUE'], 'nsrecord': [dnszone1_mname], @@ -413,7 +413,7 @@ class test_dns(Declarative): 'value': dnszone1, 'summary': None, 'result': { - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [dnszone1], 'idnszoneactive': [u'FALSE'], 'nsrecord': [dnszone1_mname], @@ -449,7 +449,7 @@ class test_dns(Declarative): 'value': dnszone1, 'summary': None, 'result': { - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [dnszone1], 'idnszoneactive': [u'TRUE'], 'nsrecord': [dnszone1_mname], @@ -507,7 +507,7 @@ class test_dns(Declarative): 'value': dnsres1, 'summary': None, 'result': { - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'objectclass': objectclasses.dnsrecord, 'arecord': [u'127.0.0.1'], @@ -525,17 +525,17 @@ class test_dns(Declarative): 'truncated': False, 'result': [ { - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'nsrecord': (dnszone1_mname,), 'idnsname': [u'@'], }, { - 'dn': unicode(dnszone1_mname_dn), + 'dn': dnszone1_mname_dn, 'idnsname': [u'ns1'], 'arecord': [u'1.2.3.4'], }, { - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'arecord': [u'127.0.0.1'], }, @@ -551,7 +551,7 @@ class test_dns(Declarative): 'value': dnsres1, 'summary': None, 'result': { - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'arecord': [u'127.0.0.1', u'10.10.0.1'], 'objectclass': objectclasses.dnsrecord, @@ -633,7 +633,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnszone, - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [u'@'], 'mxrecord': [u"0 %s" % dnszone1_mname], 'nsrecord': [dnszone1_mname], @@ -681,7 +681,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(DN(('idnsname', u'_foo._tcp'), dnszone1_dn)), + 'dn': DN(('idnsname', u'_foo._tcp'), dnszone1_dn), 'idnsname': [u'_foo._tcp'], 'srvrecord': [u"0 100 1234 %s" % dnszone1_mname], }, @@ -738,7 +738,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnszone, - 'dn': unicode(dnszone1_dn), + 'dn': dnszone1_dn, 'idnsname': [u'@'], 'mxrecord': [u"0 %s" % dnszone1_mname], 'nsrecord': [dnszone1_mname], @@ -770,7 +770,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsrescname_dn), + 'dn': dnsrescname_dn, 'idnsname': [dnsrescname], 'cnamerecord': [u'foo-1.example.com.'], }, @@ -821,7 +821,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'arecord': [u'10.10.0.1'], 'kxrecord': [u'1 foo-1'], @@ -837,7 +837,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'arecord': [u'10.10.0.1'], 'kxrecord': [u'1 foo-1'], @@ -856,7 +856,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'arecord': [u'10.10.0.1'], 'kxrecord': [u'1 foo-1'], @@ -887,7 +887,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'arecord': [u'10.10.0.1'], 'kxrecord': [u'1 foo-1'], @@ -962,7 +962,7 @@ class test_dns(Declarative): 'value': revdnszone1, 'summary': None, 'result': { - 'dn': unicode(revdnszone1_dn), + 'dn': revdnszone1_dn, 'idnsname': [revdnszone1], 'idnszoneactive': [u'TRUE'], 'idnssoamname': [dnszone1_mname], @@ -1000,7 +1000,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsrev1_dn), + 'dn': dnsrev1_dn, 'idnsname': [dnsrev1], 'ptrrecord': [u'foo-1.example.com.'], }, @@ -1015,7 +1015,7 @@ class test_dns(Declarative): 'summary': None, 'result': { 'objectclass': objectclasses.dnsrecord, - 'dn': unicode(dnsrev1_dn), + 'dn': dnsrev1_dn, 'idnsname': [dnsrev1], 'ptrrecord': [u'foo-1.example.com.'], 'cnamerecord': [u'foo-1.example.com.'], @@ -1120,7 +1120,7 @@ class test_dns(Declarative): 'value': dnsres1, 'summary': None, 'result': { - 'dn': unicode(dnsres1_dn), + 'dn': dnsres1_dn, 'idnsname': [dnsres1], 'objectclass': objectclasses.dnsrecord, 'arecord': [u'80.142.15.81'], @@ -1136,7 +1136,7 @@ class test_dns(Declarative): 'value': dnsrev2, 'summary': None, 'result': { - 'dn': unicode(dnsrev2_dn), + 'dn': dnsrev2_dn, 'idnsname': [dnsrev2], 'ptrrecord': [dnsres1 + '.' + dnszone1 + '.'], }, @@ -1183,7 +1183,7 @@ class test_dns(Declarative): value=dnszone1_permission, summary=None, result={ - 'dn': lambda x: DN(x) == dnszone1_permission_dn, + 'dn': dnszone1_permission_dn, 'cn': [dnszone1_permission], 'ipapermissiontype': [u'SYSTEM'], }, @@ -1232,4 +1232,3 @@ class test_dns(Declarative): ), ] - diff --git a/tests/test_xmlrpc/test_group_plugin.py b/tests/test_xmlrpc/test_group_plugin.py index 08cf74185..de4635d3c 100644 --- a/tests/test_xmlrpc/test_group_plugin.py +++ b/tests/test_xmlrpc/test_group_plugin.py @@ -24,7 +24,7 @@ Test the `ipalib/plugins/group.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN group1 = u'testgroup1' group2 = u'testgroup2' @@ -48,6 +48,7 @@ class test_group(Declarative): ('group_del', [group1], {}), ('group_del', [group2], {}), ('group_del', [group3], {}), + ('group_del', [renamedgroup1], {}), ('user_del', [user1], {}), ] @@ -96,7 +97,7 @@ class test_group(Declarative): description=[u'Test desc 1'], objectclass=objectclasses.group, ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == get_group_dn('testgroup1'), + dn=get_group_dn('testgroup1'), ), ), ), @@ -121,7 +122,7 @@ class test_group(Declarative): result=dict( cn=[group1], description=[u'Test desc 1'], - dn=lambda x: DN(x) == get_group_dn('testgroup1'), + dn=get_group_dn('testgroup1'), ), ), ), @@ -151,7 +152,7 @@ class test_group(Declarative): result=dict( cn=[group1], description=[u'New desc 1'], - dn=lambda x: DN(x) == get_group_dn('testgroup1'), + dn=get_group_dn('testgroup1'), ), summary=None, ), @@ -184,7 +185,7 @@ class test_group(Declarative): result=dict( cn=[group1], description=(u'New desc 1',), - dn=lambda x: DN(x) == get_group_dn('testgroup1'), + dn=get_group_dn('testgroup1'), gidnumber=[fuzzy_digits], ), summary=None, @@ -200,7 +201,7 @@ class test_group(Declarative): truncated=False, result=[ dict( - dn=lambda x: DN(x) == get_group_dn(group1), + dn=get_group_dn(group1), cn=[group1], description=[u'New desc 1'], gidnumber=[fuzzy_digits], @@ -249,7 +250,7 @@ class test_group(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == get_group_dn('testgroup2'), + dn=get_group_dn('testgroup2'), ), ), ), @@ -275,7 +276,7 @@ class test_group(Declarative): cn=[group2], description=[u'Test desc 2'], gidnumber=[fuzzy_digits], - dn=lambda x: DN(x) == get_group_dn('testgroup2'), + dn=get_group_dn('testgroup2'), ), ), ), @@ -307,7 +308,7 @@ class test_group(Declarative): cn=[group2], description=[u'New desc 2'], gidnumber=[fuzzy_digits], - dn=lambda x: DN(x) == get_group_dn('testgroup2'), + dn=get_group_dn('testgroup2'), ), summary=None, ), @@ -322,7 +323,7 @@ class test_group(Declarative): truncated=False, result=[ dict( - dn=lambda x: DN(x) == get_group_dn('testgroup2'), + dn=get_group_dn('testgroup2'), cn=[group2], description=[u'New desc 2'], gidnumber=[fuzzy_digits], @@ -342,37 +343,37 @@ class test_group(Declarative): truncated=False, result=[ { - 'dn': lambda x: DN(x) == get_group_dn('admins'), + 'dn': get_group_dn('admins'), 'member_user': [u'admin'], 'gidnumber': [fuzzy_digits], 'cn': [u'admins'], 'description': [u'Account administrators group'], }, { - 'dn': lambda x: DN(x) == get_group_dn('editors'), + 'dn': get_group_dn('editors'), 'gidnumber': [fuzzy_digits], 'cn': [u'editors'], 'description': [u'Limited admins who can edit other users'], }, { - 'dn': lambda x: DN(x) == get_group_dn('ipausers'), + 'dn': get_group_dn('ipausers'), 'cn': [u'ipausers'], 'description': [u'Default group for all users'], }, dict( - dn=lambda x: DN(x) == get_group_dn(group1), + dn=get_group_dn(group1), cn=[group1], description=[u'New desc 1'], gidnumber=[fuzzy_digits], ), dict( - dn=lambda x: DN(x) == get_group_dn(group2), + dn=get_group_dn(group2), cn=[group2], description=[u'New desc 2'], gidnumber=[fuzzy_digits], ), { - 'dn': lambda x: DN(x) == get_group_dn('trust admins'), + 'dn': get_group_dn('trust admins'), 'member_user': [u'admin'], 'cn': [u'trust admins'], 'description': [u'Trusts administrators group'], @@ -396,7 +397,7 @@ class test_group(Declarative): description=[u'Test desc 3'], objectclass=objectclasses.externalgroup, ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == get_group_dn(group3), + dn=get_group_dn(group3), ), ), ), @@ -456,7 +457,7 @@ class test_group(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == get_group_dn(group1), + 'dn': get_group_dn(group1), 'member_group': (group2,), 'gidnumber': [fuzzy_digits], 'cn': [group1], @@ -480,7 +481,7 @@ class test_group(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == get_group_dn(group1), + 'dn': get_group_dn(group1), 'member_group': (group2,), 'gidnumber': [fuzzy_digits], 'cn': [group1], @@ -503,7 +504,7 @@ class test_group(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == get_group_dn(group1), + 'dn': get_group_dn(group1), 'cn': [group1], 'gidnumber': [fuzzy_digits], 'description': [u'New desc 1'], @@ -526,7 +527,7 @@ class test_group(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == get_group_dn(group1), + 'dn': get_group_dn(group1), 'cn': [group1], 'gidnumber': [fuzzy_digits], 'description': [u'New desc 1'], @@ -691,15 +692,12 @@ class test_group(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [get_group_dn(user1)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], memberof_group=[u'ipausers'], - dn=lambda x: DN(x) == \ - DN(('uid',user1),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), + api.env.basedn), has_keytab=False, has_password=False, ), @@ -717,7 +715,7 @@ class test_group(Declarative): cn=[user1], description=[u'User private group for %s' % user1], gidnumber=[fuzzy_digits], - dn=lambda x: DN(x) == get_group_dn(user1), + dn=get_group_dn(user1), ), ), ), @@ -731,7 +729,7 @@ class test_group(Declarative): truncated=False, result=[ dict( - dn=lambda x: DN(x) == get_group_dn(user1), + dn=get_group_dn(user1), cn=[user1], description=[u'User private group for %s' % user1], gidnumber=[fuzzy_digits], @@ -810,12 +808,10 @@ class test_group(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, @@ -848,7 +844,7 @@ class test_group(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == get_group_dn('admins'), + 'dn': get_group_dn('admins'), 'member_user': [u'admin', user1], 'gidnumber': [fuzzy_digits], 'cn': [u'admins'], diff --git a/tests/test_xmlrpc/test_hbacsvcgroup_plugin.py b/tests/test_xmlrpc/test_hbacsvcgroup_plugin.py index 1552f46ce..591c8a966 100644 --- a/tests/test_xmlrpc/test_hbacsvcgroup_plugin.py +++ b/tests/test_xmlrpc/test_hbacsvcgroup_plugin.py @@ -24,7 +24,7 @@ Test the `ipalib.plugins.hbacsvcgroup` module. from ipalib import api, errors from tests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid from tests.test_xmlrpc import objectclasses -from ipalib.dn import * +from ipapython.dn import DN hbacsvcgroup1 = u'testhbacsvcgroup1' dn1 = DN(('cn',hbacsvcgroup1),('cn','hbacservicegroups'),('cn','hbac'), @@ -79,7 +79,7 @@ class test_hbacsvcgroup(Declarative): value=hbacsvcgroup1, summary=u'Added HBAC service group "testhbacsvcgroup1"', result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, cn=[hbacsvcgroup1], objectclass=objectclasses.hbacsvcgroup, description=[u'Test hbacsvcgroup 1'], @@ -111,7 +111,7 @@ class test_hbacsvcgroup(Declarative): value=hbacsvc1, summary=u'Added HBAC service "%s"' % hbacsvc1, result=dict( - dn=lambda x: DN(x) == hbacsvc_dn1, + dn=hbacsvc_dn1, cn=[hbacsvc1], description=[u'Test service 1'], objectclass=objectclasses.hbacsvc, @@ -134,7 +134,7 @@ class test_hbacsvcgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'cn': [hbacsvcgroup1], 'description': [u'Test hbacsvcgroup 1'], 'member_hbacsvc': [hbacsvc1], @@ -150,7 +150,7 @@ class test_hbacsvcgroup(Declarative): value=hbacsvcgroup1, summary=None, result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'member_hbacsvc': [hbacsvc1], 'cn': [hbacsvcgroup1], 'description': [u'Test hbacsvcgroup 1'], @@ -168,7 +168,7 @@ class test_hbacsvcgroup(Declarative): summary=u'1 HBAC service group matched', result=[ { - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'member_hbacsvc': [hbacsvc1], 'cn': [hbacsvcgroup1], 'description': [u'Test hbacsvcgroup 1'], @@ -202,7 +202,7 @@ class test_hbacsvcgroup(Declarative): value=hbacsvcgroup1, summary=None, result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'member_hbacsvc': [hbacsvc1], 'cn': [hbacsvcgroup1], 'description': [u'Updated hbacsvcgroup 1'], @@ -224,7 +224,7 @@ class test_hbacsvcgroup(Declarative): ), completed=1, result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'cn': [hbacsvcgroup1], 'description': [u'Updated hbacsvcgroup 1'], }, diff --git a/tests/test_xmlrpc/test_host_plugin.py b/tests/test_xmlrpc/test_host_plugin.py index c9ec6a2bd..27d3adb04 100644 --- a/tests/test_xmlrpc/test_host_plugin.py +++ b/tests/test_xmlrpc/test_host_plugin.py @@ -26,7 +26,7 @@ import os import tempfile from ipapython import ipautil from ipalib import api, errors, x509 -from ipalib.dn import * +from ipapython.dn import DN from nose.tools import raises, assert_raises from nose.plugins.skip import Skip, SkipTest from tests.test_xmlrpc.xmlrpc_test import (Declarative, XMLRPC_test, @@ -111,7 +111,7 @@ class test_host(Declarative): value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -147,7 +147,7 @@ class test_host(Declarative): value=fqdn1, summary=None, result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -167,7 +167,7 @@ class test_host(Declarative): value=fqdn1, summary=None, result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, cn=[fqdn1], fqdn=[fqdn1], description=[u'Test host 1'], @@ -198,7 +198,7 @@ class test_host(Declarative): summary=u'1 host matched', result=[ dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -221,7 +221,7 @@ class test_host(Declarative): summary=u'1 host matched', result=[ dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, cn=[fqdn1], fqdn=[fqdn1], description=[u'Test host 1'], @@ -260,8 +260,7 @@ class test_host(Declarative): usercertificate=[base64.b64decode(servercert)], valid_not_before=fuzzy_date, valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), + subject=DN(('CN',api.env.host),x509.subject_base()), serial_number=fuzzy_digits, serial_number_hex=fuzzy_hex, md5_fingerprint=fuzzy_hash, @@ -281,7 +280,7 @@ class test_host(Declarative): value=fqdn1, summary=None, result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, fqdn=[fqdn1], description=[u'Updated host 1'], l=[u'Undisclosed location 1'], @@ -292,8 +291,7 @@ class test_host(Declarative): usercertificate=[base64.b64decode(servercert)], valid_not_before=fuzzy_date, valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), + subject=DN(('CN',api.env.host),x509.subject_base()), serial_number=fuzzy_digits, serial_number_hex=fuzzy_hex, md5_fingerprint=fuzzy_hash, @@ -316,7 +314,7 @@ class test_host(Declarative): value=fqdn3, summary=u'Added host "%s"' % fqdn3, result=dict( - dn=lambda x: DN(x) == dn3, + dn=dn3, fqdn=[fqdn3], description=[u'Test host 2'], l=[u'Undisclosed location 2'], @@ -344,7 +342,7 @@ class test_host(Declarative): value=fqdn4, summary=u'Added host "%s"' % fqdn4, result=dict( - dn=lambda x: DN(x) == dn4, + dn=dn4, fqdn=[fqdn4], description=[u'Test host 4'], l=[u'Undisclosed location 4'], @@ -374,7 +372,7 @@ class test_host(Declarative): ), ), result=dict( - dn=lambda x: DN(x) == dn3, + dn=dn3, fqdn=[fqdn3], description=[u'Test host 2'], l=[u'Undisclosed location 2'], @@ -391,7 +389,7 @@ class test_host(Declarative): value=fqdn3, summary=None, result=dict( - dn=lambda x: DN(x) == dn3, + dn=dn3, fqdn=[fqdn3], description=[u'Test host 2'], l=[u'Undisclosed location 2'], @@ -412,7 +410,7 @@ class test_host(Declarative): summary=u'1 host matched', result=[ dict( - dn=lambda x: DN(x) == dn3, + dn=dn3, fqdn=[fqdn3], description=[u'Test host 2'], l=[u'Undisclosed location 2'], @@ -451,7 +449,7 @@ class test_host(Declarative): ), ), result=dict( - dn=lambda x: DN(x) == dn3, + dn=dn3, fqdn=[fqdn3], description=[u'Test host 2'], l=[u'Undisclosed location 2'], @@ -491,8 +489,7 @@ class test_host(Declarative): usercertificate=[base64.b64decode(servercert)], valid_not_before=fuzzy_date, valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), + subject=DN(('CN',api.env.host),x509.subject_base()), serial_number=fuzzy_digits, serial_number_hex=fuzzy_hex, md5_fingerprint=fuzzy_hash, @@ -521,8 +518,7 @@ class test_host(Declarative): usercertificate=[base64.b64decode(servercert)], valid_not_before=fuzzy_date, valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), + subject=DN(('CN',api.env.host),x509.subject_base()), serial_number=fuzzy_digits, serial_number_hex=fuzzy_hex, md5_fingerprint=fuzzy_hash, @@ -591,7 +587,7 @@ class test_host(Declarative): value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -612,7 +608,7 @@ class test_host(Declarative): value=service1, summary=u'Added service "%s"' % service1, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], objectclass=objectclasses.service, managedby_host=[fqdn1], @@ -666,7 +662,7 @@ class test_host(Declarative): value=fqdn2, summary=u'Added host "%s"' % fqdn2, result=dict( - dn=lambda x: DN(x) == dn2, + dn=dn2, fqdn=[fqdn2], description=[u'Test host 2'], l=[u'Undisclosed location 2'], diff --git a/tests/test_xmlrpc/test_hostgroup_plugin.py b/tests/test_xmlrpc/test_hostgroup_plugin.py index 5642b1097..36c497ba8 100644 --- a/tests/test_xmlrpc/test_hostgroup_plugin.py +++ b/tests/test_xmlrpc/test_hostgroup_plugin.py @@ -23,10 +23,9 @@ Test the `ipalib.plugins.hostgroup` module. """ from ipalib import api, errors -from ipalib.dn import * from tests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid from tests.test_xmlrpc import objectclasses -from ipalib.dn import * +from ipapython.dn import DN hostgroup1 = u'testhostgroup1' dn1 = DN(('cn',hostgroup1),('cn','hostgroups'),('cn','accounts'), @@ -95,14 +94,13 @@ class test_hostgroup(Declarative): value=hostgroup1, summary=u'Added hostgroup "testhostgroup1"', result=dict( - dn=lambda x: DN(x) == dn1, + dn=dn1, cn=[hostgroup1], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], ipauniqueid=[fuzzy_uuid], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), - api.env.basedn)], + mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), + api.env.basedn)], ), ), ), @@ -131,7 +129,7 @@ class test_hostgroup(Declarative): value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( - dn=lambda x: DN(x) == host_dn1, + dn=host_dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -160,7 +158,7 @@ class test_hostgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], 'member_host': [fqdn1], @@ -176,7 +174,7 @@ class test_hostgroup(Declarative): value=hostgroup1, summary=None, result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'member_host': [u'testhost1.%s' % api.env.domain], 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], @@ -194,7 +192,7 @@ class test_hostgroup(Declarative): summary=u'1 hostgroup matched', result=[ { - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'member_host': [u'testhost1.%s' % api.env.domain], 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], @@ -228,7 +226,7 @@ class test_hostgroup(Declarative): value=hostgroup1, summary=None, result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'member_host': [u'testhost1.%s' % api.env.domain], 'cn': [hostgroup1], 'description': [u'Updated hostgroup 1'], @@ -251,7 +249,7 @@ class test_hostgroup(Declarative): ), completed=1, result={ - 'dn': lambda x: DN(x) == dn1, + 'dn': dn1, 'cn': [hostgroup1], 'description': [u'Updated hostgroup 1'], }, @@ -279,14 +277,13 @@ class test_hostgroup(Declarative): value=hostgroup_single, summary=u'Added hostgroup "a"', result=dict( - dn=lambda x: DN(x) == dn_single, + dn=dn_single, cn=[hostgroup_single], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup with single letter in name'], ipauniqueid=[fuzzy_uuid], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',hostgroup_single),('cn','ng'),('cn','alt'), - api.env.basedn)], + mepmanagedentry=[DN(('cn',hostgroup_single),('cn','ng'),('cn','alt'), + api.env.basedn)], ), ), ), diff --git a/tests/test_xmlrpc/test_krbtpolicy.py b/tests/test_xmlrpc/test_krbtpolicy.py index 26ba87c47..fc2bf54cf 100644 --- a/tests/test_xmlrpc/test_krbtpolicy.py +++ b/tests/test_xmlrpc/test_krbtpolicy.py @@ -23,7 +23,7 @@ Test kerberos ticket policy from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN user1 = u'tuser1' @@ -61,9 +61,8 @@ class test_krbtpolicy(Declarative): value=u'', summary=None, result=dict( - dn=lambda x: DN(x) == \ - DN(('cn',api.env.domain),('cn','kerberos'), - api.env.basedn), + dn=DN(('cn',api.env.domain),('cn','kerberos'), + api.env.basedn), krbmaxticketlife=[u'86400'], krbmaxrenewableage=[u'604800'], ), @@ -110,18 +109,14 @@ class test_krbtpolicy(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user1),('cn','users'),('cn','accounts'), - api.env.basedn) + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), api.env.basedn) ), ), ), diff --git a/tests/test_xmlrpc/test_nesting.py b/tests/test_xmlrpc/test_nesting.py index a855960a1..c28b7096b 100644 --- a/tests/test_xmlrpc/test_nesting.py +++ b/tests/test_xmlrpc/test_nesting.py @@ -23,7 +23,7 @@ Test group nexting an indirect members from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN group1 = u'testgroup1' group2 = u'testgroup2' @@ -80,9 +80,8 @@ class test_nesting(Declarative): objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], gidnumber=[fuzzy_digits], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup1'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup1'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -104,9 +103,8 @@ class test_nesting(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup2'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup2'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -126,9 +124,8 @@ class test_nesting(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup3'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup3'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -148,9 +145,8 @@ class test_nesting(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup4'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup4'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -179,18 +175,15 @@ class test_nesting(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user1),('cn','users'),('cn','accounts'), - api.env.basedn) + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), + api.env.basedn) ), ), ), @@ -219,18 +212,15 @@ class test_nesting(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user2),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user2),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user2),('cn','users'),('cn','accounts'), - api.env.basedn) + dn=DN(('uid',user2),('cn','users'),('cn','accounts'), + api.env.basedn) ), ), ), @@ -259,18 +249,15 @@ class test_nesting(Declarative): cn=[u'Test User3'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user3),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user3),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user3),('cn','users'),('cn','accounts'), - api.env.basedn) + dn=DN(('uid',user3),('cn','users'),('cn','accounts'), + api.env.basedn) ), ), ), @@ -299,18 +286,15 @@ class test_nesting(Declarative): cn=[u'Test User4'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user4),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user4),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user4),('cn','users'),('cn','accounts'), - api.env.basedn) + dn=DN(('uid',user4),('cn','users'),('cn','accounts'), + api.env.basedn) ), ), ), @@ -375,9 +359,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_group': (group2,), 'gidnumber': [fuzzy_digits], 'cn': [group1], @@ -401,9 +384,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_group': [group2, group3,], 'gidnumber': [fuzzy_digits], 'cn': [group1], @@ -427,9 +409,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group2),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group2),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': (u'tuser1',), 'memberof_group': (u'testgroup1',), 'gidnumber': [fuzzy_digits], @@ -454,9 +435,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group2),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group2),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': [user1, user2], 'memberof_group': [group1], 'gidnumber': [fuzzy_digits], @@ -481,9 +461,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group3),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group3),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': [user3], 'memberof_group': [group1], 'gidnumber': [fuzzy_digits], @@ -508,9 +487,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group3),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group3),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': [user3], 'memberof_group': [group1], 'member_group': [group4], @@ -536,9 +514,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group4),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group4),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': [user1], 'memberof_group': [group3], 'memberofindirect_group': [group1], @@ -564,9 +541,8 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group4),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group4),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': [user1, user4], 'memberof_group': [group3], 'memberofindirect_group': [group1], @@ -591,9 +567,8 @@ class test_nesting(Declarative): memberindirect_group = [group4], member_group = [group2, group3], memberindirect_user = [user1, user2, user3, user4], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup1'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup1'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -611,9 +586,8 @@ class test_nesting(Declarative): gidnumber= [fuzzy_digits], memberof_group = [group1], member_user = [user1, user2], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup2'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup2'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -633,9 +607,8 @@ class test_nesting(Declarative): member_user = [user3], member_group = [group4], memberindirect_user = [user1, user4], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup3'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup3'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -654,9 +627,8 @@ class test_nesting(Declarative): memberof_group = [group3], member_user = [user1, user4], memberofindirect_group = [group1], - dn=lambda x: DN(x) == \ - DN(('cn','testgroup4'),('cn','groups'), - ('cn','accounts'),api.env.basedn), + dn=DN(('cn','testgroup4'),('cn','groups'), + ('cn','accounts'),api.env.basedn), ), ), ), @@ -676,7 +648,7 @@ class test_nesting(Declarative): value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( - dn=lambda x: DN(x) == host_dn1, + dn=host_dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -700,14 +672,13 @@ class test_nesting(Declarative): value=hostgroup1, summary=u'Added hostgroup "testhostgroup1"', result=dict( - dn=lambda x: DN(x) == hgdn1, + dn=hgdn1, cn=[hostgroup1], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], ipauniqueid=[fuzzy_uuid], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), - api.env.basedn)], + mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), + api.env.basedn)], ), ), ), @@ -722,14 +693,13 @@ class test_nesting(Declarative): value=hostgroup2, summary=u'Added hostgroup "testhostgroup2"', result=dict( - dn=lambda x: DN(x) == hgdn2, + dn=hgdn2, cn=[hostgroup2], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 2'], ipauniqueid=[fuzzy_uuid], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',hostgroup2),('cn','ng'),('cn','alt'), - api.env.basedn)], + mepmanagedentry=[DN(('cn',hostgroup2),('cn','ng'),('cn','alt'), + api.env.basedn)], ), ), ), @@ -749,7 +719,7 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == hgdn2, + 'dn': hgdn2, 'cn': [hostgroup2], 'description': [u'Test hostgroup 2'], 'member_host': [fqdn1], @@ -772,7 +742,7 @@ class test_nesting(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == hgdn1, + 'dn': hgdn1, 'cn': [hostgroup1], 'description': [u'Test hostgroup 1'], 'member_hostgroup': [hostgroup2], @@ -788,7 +758,7 @@ class test_nesting(Declarative): value=hostgroup1, summary=None, result={ - 'dn': lambda x: DN(x) == hgdn1, + 'dn': hgdn1, 'memberindirect_host': [u'testhost1.%s' % api.env.domain], 'member_hostgroup': [hostgroup2], 'cn': [hostgroup1], @@ -805,7 +775,7 @@ class test_nesting(Declarative): value=fqdn1, summary=None, result=dict( - dn=lambda x: DN(x) == host_dn1, + dn=host_dn1, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], diff --git a/tests/test_xmlrpc/test_netgroup_plugin.py b/tests/test_xmlrpc/test_netgroup_plugin.py index 951bc77a3..b54291a64 100644 --- a/tests/test_xmlrpc/test_netgroup_plugin.py +++ b/tests/test_xmlrpc/test_netgroup_plugin.py @@ -28,7 +28,7 @@ from ipalib import errors from ipaserver.plugins.ldap2 import ldap2 from tests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_netgroupdn from tests.test_xmlrpc import objectclasses -from ipalib.dn import * +from ipapython.dn import DN # Global so we can save the value between tests netgroup_dn = None @@ -142,7 +142,6 @@ class test_netgroup(Declarative): value=netgroup1, summary=u'Added netgroup "%s"' % netgroup1, result=dict( -# dn=u'ipauniqueid=%s,cn=ng,cn=alt,%s' % (fuzzy_uuid, api.env.basedn), dn=fuzzy_netgroupdn, cn=[netgroup1], objectclass=objectclasses.netgroup, @@ -163,7 +162,6 @@ class test_netgroup(Declarative): value=netgroup2, summary=u'Added netgroup "%s"' % netgroup2, result=dict( -# dn=u'ipauniqueid=%s,cn=ng,cn=alt,%s' % (fuzzy_uuid, api.env.basedn), dn=fuzzy_netgroupdn, cn=[netgroup2], objectclass=objectclasses.netgroup, @@ -229,7 +227,7 @@ class test_netgroup(Declarative): value=host1, summary=u'Added host "%s"' % host1, result=dict( - dn=lambda x: DN(x) == host_dn1, + dn=host_dn1, fqdn=[host1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -253,13 +251,12 @@ class test_netgroup(Declarative): value=hostgroup1, summary=u'Added hostgroup "%s"' % hostgroup1, result=dict( - dn=lambda x: DN(x) == hostgroup_dn1, + dn=hostgroup_dn1, cn=[hostgroup1], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), - api.env.basedn)], + mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), + api.env.basedn)], ipauniqueid=[fuzzy_uuid], ), ), @@ -289,18 +286,15 @@ class test_netgroup(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user1),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -328,18 +322,15 @@ class test_netgroup(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user2),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user2),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid',user2),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid',user2),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -359,9 +350,8 @@ class test_netgroup(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + dn=DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -381,9 +371,8 @@ class test_netgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': (user2,), 'gidnumber': [fuzzy_digits], 'cn': [group1], diff --git a/tests/test_xmlrpc/test_permission_plugin.py b/tests/test_xmlrpc/test_permission_plugin.py index 8aaa4a999..a1062f085 100644 --- a/tests/test_xmlrpc/test_permission_plugin.py +++ b/tests/test_xmlrpc/test_permission_plugin.py @@ -25,7 +25,7 @@ Test the `ipalib/plugins/permission.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN permission1 = u'testperm' permission1_dn = DN(('cn',permission1), @@ -110,7 +110,7 @@ class test_permission(Declarative): value=permission1, summary=u'Added permission "%s"' % permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], objectclass=objectclasses.permission, type=u'user', @@ -141,7 +141,7 @@ class test_permission(Declarative): value=privilege1, summary=u'Added privilege "%s"' % privilege1, result=dict( - dn=lambda x: DN(x) == privilege1_dn, + dn=privilege1_dn, cn=[privilege1], description=[u'privilege desc. 1'], objectclass=objectclasses.privilege, @@ -163,7 +163,7 @@ class test_permission(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1], @@ -179,7 +179,7 @@ class test_permission(Declarative): value=permission1, summary=None, result={ - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -196,11 +196,12 @@ class test_permission(Declarative): value=permission1, summary=None, result={ - 'dn': unicode(permission1_dn), + 'dn': permission1_dn, 'cn': [permission1], - 'member': [unicode(privilege1_dn)], - 'aci': u'(target = "ldap:///uid=*,cn=users,cn=accounts,%s")(version 3.0;acl "permission:testperm";allow (write) groupdn = "ldap:///cn=testperm,cn=permissions,cn=pbac,%s";)' \ - % (api.env.basedn, api.env.basedn), + 'member': [privilege1_dn], + 'aci': u'(target = "ldap:///%s")(version 3.0;acl "permission:testperm";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('uid', '*'), ('cn', 'users'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'testperm'), ('cn', 'permissions'), ('cn', 'pbac'), api.env.basedn)) }, ), ), @@ -215,7 +216,7 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -235,7 +236,7 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -267,7 +268,7 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -287,11 +288,12 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': unicode(permission1_dn), + 'dn': permission1_dn, 'cn': [permission1], - 'member': [unicode(privilege1_dn)], - 'aci': u'(target = "ldap:///uid=*,cn=users,cn=accounts,%s")(version 3.0;acl "permission:testperm";allow (write) groupdn = "ldap:///cn=testperm,cn=permissions,cn=pbac,%s";)' \ - % (api.env.basedn, api.env.basedn), + 'member': [privilege1_dn], + 'aci': u'(target = "ldap:///%s")(version 3.0;acl "permission:testperm";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('uid', '*'), ('cn', 'users'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'testperm'), ('cn', 'permissions'), ('cn', 'pbac'), api.env.basedn)), }, ], ), @@ -312,7 +314,7 @@ class test_permission(Declarative): value=permission2, summary=u'Added permission "%s"' % permission2, result=dict( - dn=lambda x: DN(x) == permission2_dn, + dn=permission2_dn, cn=[permission2], objectclass=objectclasses.permission, type=u'user', @@ -332,14 +334,14 @@ class test_permission(Declarative): summary=u'2 permissions matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', 'permissions': [u'write'], }, { - 'dn': lambda x: DN(x) == permission2_dn, + 'dn': permission2_dn, 'cn': [permission2], 'type': u'user', 'permissions': [u'write'], @@ -358,11 +360,11 @@ class test_permission(Declarative): summary=u'2 permissions matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], }, { - 'dn': lambda x: DN(x) == permission2_dn, + 'dn': permission2_dn, 'cn': [permission2], }, ], @@ -380,8 +382,8 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': lambda x: DN(x) == DN(('cn','Modify Group Password Policy'), - api.env.container_permission,api.env.basedn), + 'dn': DN(('cn','Modify Group Password Policy'), + api.env.container_permission, api.env.basedn), 'cn': [u'Modify Group Password Policy'], }, ], @@ -398,7 +400,7 @@ class test_permission(Declarative): summary=u'1 privilege matched', result=[ { - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1], @@ -417,7 +419,7 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -437,14 +439,14 @@ class test_permission(Declarative): summary=u'2 permissions matched', result=[ { - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', 'permissions': [u'write'], }, { - 'dn': lambda x: DN(x) == permission2_dn, + 'dn': permission2_dn, 'cn': [permission2], 'type': u'user', 'permissions': [u'write'], @@ -468,14 +470,14 @@ class test_permission(Declarative): summary=u'1 permission matched', result=[ { - 'dn': lambda x: DN(x) == DN(('cn', 'Modify HBAC rule'), - api.env.container_permission,api.env.basedn), + 'dn': DN(('cn', 'Modify HBAC rule'), + api.env.container_permission, api.env.basedn), 'cn': [u'Modify HBAC rule'], 'member_privilege': [u'HBAC Administrator'], 'permissions' : [u'write'], 'attrs': [u'servicecategory', u'sourcehostcategory', u'cn', u'description', u'ipaenabledflag', u'accesstime', u'usercategory', u'hostcategory', u'accessruletype', u'sourcehost'], - 'subtree' : u'ldap:///ipauniqueid=*,cn=hbac,%s' % api.env.basedn, - 'memberindirect': [u'cn=hbac administrator,cn=privileges,cn=pbac,%s' % api.env.basedn, u'cn=it security specialist,cn=roles,cn=accounts,%s' % api.env.basedn], + 'subtree' : u'ldap:///%s' % DN(('ipauniqueid', '*'), ('cn', 'hbac'), api.env.basedn), + 'memberindirect': [DN(('cn', 'it security specialist'), ('cn', 'roles'), ('cn', 'accounts'), api.env.basedn)], }, ], ), @@ -496,7 +498,7 @@ class test_permission(Declarative): value=permission1, summary=u'Modified permission "%s"' % permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], member_privilege=[privilege1], type=u'user', @@ -515,7 +517,7 @@ class test_permission(Declarative): value=permission1, summary=None, result={ - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -556,7 +558,7 @@ class test_permission(Declarative): value=permission1, summary=None, result={ - 'dn': lambda x: DN(x) == permission1_dn, + 'dn': permission1_dn, 'cn': [permission1], 'member_privilege': [privilege1], 'type': u'user', @@ -578,7 +580,7 @@ class test_permission(Declarative): value=permission1, summary=u'Modified permission "%s"' % permission1, result={ - 'dn': lambda x: DN(x) == permission1_renamed_dn, + 'dn': permission1_renamed_dn, 'cn': [permission1_renamed], 'member_privilege': [privilege1], 'type': u'user', @@ -600,7 +602,7 @@ class test_permission(Declarative): value=permission1_renamed, summary=u'Modified permission "%s"' % permission1_renamed, result={ - 'dn': lambda x: DN(x) == permission1_renamed_ucase_dn, + 'dn': permission1_renamed_ucase_dn, 'cn': [permission1_renamed_ucase], 'member_privilege': [privilege1], 'type': u'user', @@ -614,16 +616,18 @@ class test_permission(Declarative): dict( desc='Change %r to a subtree type' % permission1_renamed_ucase, command=( - 'permission_mod', [permission1_renamed_ucase], dict(subtree=u'ldap:///cn=*,cn=test,cn=accounts,%s' % api.env.basedn, type=None) + 'permission_mod', [permission1_renamed_ucase], + dict(subtree=u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn), + type=None) ), expected=dict( value=permission1_renamed_ucase, summary=u'Modified permission "%s"' % permission1_renamed_ucase, result=dict( - dn=lambda x: DN(x) == permission1_renamed_ucase_dn, + dn=permission1_renamed_ucase_dn, cn=[permission1_renamed_ucase], member_privilege=[privilege1], - subtree=u'ldap:///cn=*,cn=test,cn=accounts,%s' % api.env.basedn, + subtree=u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn), permissions=[u'write'], memberof=u'ipausers', ), @@ -633,17 +637,18 @@ class test_permission(Declarative): dict( desc='Search for %r using --subtree' % permission1, - command=('permission_find', [], {'subtree': 'ldap:///cn=*,cn=test,cn=accounts,%s' % api.env.basedn}), + command=('permission_find', [], + {'subtree': u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn)}), expected=dict( count=1, truncated=False, summary=u'1 permission matched', result=[ { - 'dn':lambda x: DN(x) == permission1_renamed_ucase_dn, + 'dn':permission1_renamed_ucase_dn, 'cn':[permission1_renamed_ucase], 'member_privilege':[privilege1], - 'subtree':u'ldap:///cn=*,cn=test,cn=accounts,%s' % api.env.basedn, + 'subtree':u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn), 'permissions':[u'write'], 'memberof':u'ipausers', }, @@ -756,7 +761,7 @@ class test_permission(Declarative): value=permission1, summary=u'Added permission "%s"' % permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], objectclass=objectclasses.permission, memberof=u'editors', @@ -784,7 +789,7 @@ class test_permission(Declarative): value=permission1, summary=u'Modified permission "%s"' % permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], memberof=u'admins', permissions=[u'write'], @@ -804,7 +809,7 @@ class test_permission(Declarative): summary=u'Modified permission "%s"' % permission1, value=permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], permissions=[u'write'], type=u'user', @@ -836,7 +841,7 @@ class test_permission(Declarative): value=permission1, summary=u'Added permission "%s"' % permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], objectclass=objectclasses.permission, targetgroup=u'editors', diff --git a/tests/test_xmlrpc/test_privilege_plugin.py b/tests/test_xmlrpc/test_privilege_plugin.py index d8d9b22a6..d4784af8c 100644 --- a/tests/test_xmlrpc/test_privilege_plugin.py +++ b/tests/test_xmlrpc/test_privilege_plugin.py @@ -24,7 +24,7 @@ Test the `ipalib/plugins/privilege.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN permission1 = u'testperm' permission1_dn = DN(('cn',permission1), @@ -97,7 +97,7 @@ class test_privilege(Declarative): value=permission1, summary=u'Added permission "%s"' % permission1, result=dict( - dn=lambda x: DN(x) == permission1_dn, + dn=permission1_dn, cn=[permission1], objectclass=objectclasses.permission, type=u'user', @@ -116,7 +116,7 @@ class test_privilege(Declarative): value=privilege1, summary=u'Added privilege "%s"' % privilege1, result=dict( - dn=lambda x: DN(x) == privilege1_dn, + dn=privilege1_dn, cn=[privilege1], description=[u'privilege desc. 1'], objectclass=objectclasses.privilege, @@ -138,7 +138,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1], @@ -154,7 +154,7 @@ class test_privilege(Declarative): value=privilege1, summary=None, result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1], @@ -172,7 +172,7 @@ class test_privilege(Declarative): summary=u'1 privilege matched', result=[ { - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1], @@ -191,7 +191,7 @@ class test_privilege(Declarative): summary=u'1 privilege matched', result=[ { - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1], @@ -213,7 +213,7 @@ class test_privilege(Declarative): value=permission2, summary=u'Added permission "%s"' % permission2, result=dict( - dn=lambda x: DN(x) == permission2_dn, + dn=permission2_dn, cn=[permission2], objectclass=objectclasses.permission, type=u'user', @@ -236,7 +236,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1, permission2], @@ -258,7 +258,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1, permission2], @@ -276,7 +276,7 @@ class test_privilege(Declarative): summary=u'1 privilege matched', result=[ { - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'privilege desc. 1'], 'memberof_permission': [permission1, permission2], @@ -316,7 +316,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'New desc 1'], 'memberof_permission': [permission2], @@ -338,7 +338,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'New desc 1'], 'memberof_permission': [permission2], @@ -360,7 +360,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'New desc 1'], 'memberof_permission': [permission2], @@ -382,7 +382,7 @@ class test_privilege(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == privilege1_dn, + 'dn': privilege1_dn, 'cn': [privilege1], 'description': [u'New desc 1'], 'memberof_permission': [permission2], diff --git a/tests/test_xmlrpc/test_range_plugin.py b/tests/test_xmlrpc/test_range_plugin.py index 76ffc58b7..ea4bc2b2d 100644 --- a/tests/test_xmlrpc/test_range_plugin.py +++ b/tests/test_xmlrpc/test_range_plugin.py @@ -24,7 +24,7 @@ Test the `ipalib/plugins/range.py` module, and XML-RPC in general. from ipalib import api, errors, _ from tests.util import assert_equal, Fuzzy from xmlrpc_test import Declarative -from ipalib.dn import * +from ipapython.dn import * testrange1 = u't-range-1' @@ -41,9 +41,8 @@ class test_range(Declarative): ipabaserid=1000, ipasecondarybaserid=20000)), expected=dict( result=dict( - dn=lambda x: DN(x) == \ - DN(('cn',testrange1),('cn','ranges'),('cn','etc'), - api.env.basedn), + dn=DN(('cn',testrange1),('cn','ranges'),('cn','etc'), + api.env.basedn), cn=[testrange1], objectclass=[u'ipaIDrange', u'ipadomainidrange'], ipabaseid=[u'900000'], @@ -62,9 +61,8 @@ class test_range(Declarative): command=('range_show', [testrange1], dict()), expected=dict( result=dict( - dn=lambda x: DN(x) == \ - DN(('cn',testrange1),('cn','ranges'),('cn','etc'), - api.env.basedn), + dn=DN(('cn',testrange1),('cn','ranges'),('cn','etc'), + api.env.basedn), cn=[testrange1], ipabaseid=[u'900000'], ipabaserid=[u'1000'], diff --git a/tests/test_xmlrpc/test_replace.py b/tests/test_xmlrpc/test_replace.py index f5203607c..434f71d70 100644 --- a/tests/test_xmlrpc/test_replace.py +++ b/tests/test_xmlrpc/test_replace.py @@ -28,7 +28,7 @@ gen_modlist code. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN user1=u'tuser1' @@ -66,18 +66,15 @@ class test_replace(Declarative): initials=[u'TU'], mail=[u'test1@example.com', u'test2@example.com'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm),('cn','kerberos'), - api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm),('cn','kerberos'), + api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), diff --git a/tests/test_xmlrpc/test_role_plugin.py b/tests/test_xmlrpc/test_role_plugin.py index e2bd28cd6..b847ca9eb 100644 --- a/tests/test_xmlrpc/test_role_plugin.py +++ b/tests/test_xmlrpc/test_role_plugin.py @@ -25,7 +25,7 @@ Test the `ipalib/plugins/role.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN search = u'test-role' @@ -117,7 +117,7 @@ class test_role(Declarative): value=role1, summary=u'Added role "%s"' % role1, result=dict( - dn=lambda x: DN(x) == role1_dn, + dn=role1_dn, cn=[role1], description=[u'role desc 1'], objectclass=objectclasses.role, @@ -133,7 +133,7 @@ class test_role(Declarative): value=role1, summary=None, result=dict( - dn=lambda x: DN(x) == role1_dn, + dn=role1_dn, cn=[role1], description=[u'role desc 1'], ), @@ -151,7 +151,7 @@ class test_role(Declarative): value=group1, summary=u'Added group "testgroup1"', result=dict( - dn=lambda x: DN(x) == group1_dn, + dn=group1_dn, cn=[group1], description=[u'group desc 1'], objectclass=objectclasses.group, @@ -170,7 +170,7 @@ class test_role(Declarative): value=privilege1, summary=u'Added privilege "%s"' % privilege1, result=dict( - dn=lambda x: DN(x) == privilege1_dn, + dn=privilege1_dn, cn=[privilege1], description=[u'privilege desc. 1'], objectclass=objectclasses.privilege, @@ -192,7 +192,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'memberof_privilege': [privilege1], @@ -213,7 +213,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'memberof_privilege': [privilege1], @@ -234,7 +234,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'memberof_privilege': [privilege1], @@ -257,7 +257,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'member_group': [group1], @@ -274,7 +274,7 @@ class test_role(Declarative): value=role1, summary=None, result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'member_group': [group1], @@ -293,7 +293,7 @@ class test_role(Declarative): summary=u'1 role matched', result=[ { - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'member_group': [group1], @@ -313,7 +313,7 @@ class test_role(Declarative): summary=u'1 role matched', result=[ { - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'member_group': [group1], @@ -333,7 +333,7 @@ class test_role(Declarative): value=role2, summary=u'Added role "%s"' % role2, result=dict( - dn=lambda x: DN(x) == role2_dn, + dn=role2_dn, cn=[role2], description=[u'role desc 2'], objectclass=objectclasses.role, @@ -351,7 +351,7 @@ class test_role(Declarative): summary=u'1 role matched', result=[ { - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'member_group': [group1], @@ -371,14 +371,14 @@ class test_role(Declarative): summary=u'2 roles matched', result=[ { - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'role desc 1'], 'member_group': [group1], 'memberof_privilege': [privilege1], }, { - 'dn': lambda x: DN(x) == role2_dn, + 'dn': role2_dn, 'cn': [role2], 'description': [u'role desc 2'], }, @@ -412,7 +412,7 @@ class test_role(Declarative): value=role1, summary=None, result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'New desc 1'], 'member_group': [group1], @@ -436,7 +436,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'New desc 1'], 'memberof_privilege': [privilege1], @@ -452,7 +452,7 @@ class test_role(Declarative): value=role1, summary=None, result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'New desc 1'], 'memberof_privilege': [privilege1], @@ -515,7 +515,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'New desc 1'], } @@ -536,7 +536,7 @@ class test_role(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == role1_dn, + 'dn': role1_dn, 'cn': [role1], 'description': [u'New desc 1'], } @@ -586,7 +586,7 @@ class test_role(Declarative): summary=u'1 role matched', result=[ { - 'dn': lambda x: DN(x) == role2_dn, + 'dn': role2_dn, 'cn': [role2], 'description': [u'role desc 2'], }, diff --git a/tests/test_xmlrpc/test_selinuxusermap_plugin.py b/tests/test_xmlrpc/test_selinuxusermap_plugin.py index 2f6f52c6d..c1bee54dc 100644 --- a/tests/test_xmlrpc/test_selinuxusermap_plugin.py +++ b/tests/test_xmlrpc/test_selinuxusermap_plugin.py @@ -23,7 +23,7 @@ Test the `ipalib/plugins/selinuxusermap.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN from tests.util import Fuzzy rule1 = u'selinuxrule1' @@ -34,15 +34,16 @@ user1 = u'tuser1' group1 = u'testgroup1' host1 = u'testhost1.%s' % api.env.domain hostdn1 = DN(('fqdn',host1),('cn','computers'),('cn','accounts'), - api.env.basedn) + api.env.basedn) hbacrule1 = u'testhbacrule1' hbacrule2 = u'testhbacrule12' +# Note (?i) at the beginning of the regexp is the ingnore case flag fuzzy_selinuxusermapdn = Fuzzy( - 'ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' % (api.env.container_selinux, api.env.basedn) + '(?i)ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' % (api.env.container_selinux, api.env.basedn) ) fuzzy_hbacruledn = Fuzzy( - 'ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' % (api.env.container_hbac, api.env.basedn) + '(?i)ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' % (api.env.container_hbac, api.env.basedn) ) class test_selinuxusermap(Declarative): @@ -204,16 +205,13 @@ class test_selinuxusermap(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], - dn=lambda x: DN(x) == \ - DN(('uid',user1),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), + api.env.basedn), has_keytab=False, has_password=False, ), @@ -234,9 +232,8 @@ class test_selinuxusermap(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + dn=DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -256,9 +253,8 @@ class test_selinuxusermap(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), 'member_user': (user1,), 'gidnumber': [fuzzy_digits], 'cn': [group1], @@ -281,7 +277,7 @@ class test_selinuxusermap(Declarative): value=host1, summary=u'Added host "%s"' % host1, result=dict( - dn=lambda x: DN(x) == hostdn1, + dn=hostdn1, fqdn=[host1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], diff --git a/tests/test_xmlrpc/test_service_plugin.py b/tests/test_xmlrpc/test_service_plugin.py index 28c6bb663..e06132fe1 100644 --- a/tests/test_xmlrpc/test_service_plugin.py +++ b/tests/test_xmlrpc/test_service_plugin.py @@ -27,7 +27,7 @@ from tests.test_xmlrpc.xmlrpc_test import fuzzy_digits, fuzzy_date, fuzzy_issuer from tests.test_xmlrpc.xmlrpc_test import fuzzy_hex from tests.test_xmlrpc import objectclasses import base64 -from ipalib.dn import * +from ipapython.dn import DN fqdn1 = u'testhost1.%s' % api.env.domain fqdn2 = u'testhost2.%s' % api.env.domain @@ -93,7 +93,7 @@ class test_service(Declarative): value=fqdn1, summary=u'Added host "%s"' % fqdn1, result=dict( - dn=lambda x: DN(x) == host1dn, + dn=host1dn, fqdn=[fqdn1], description=[u'Test host 1'], l=[u'Undisclosed location 1'], @@ -121,7 +121,7 @@ class test_service(Declarative): value=fqdn2, summary=u'Added host "%s"' % fqdn2, result=dict( - dn=lambda x: DN(x) == host2dn, + dn=host2dn, fqdn=[fqdn2], description=[u'Test host 2'], l=[u'Undisclosed location 2'], @@ -149,7 +149,7 @@ class test_service(Declarative): value=fqdn3.lower(), summary=u'Added host "%s"' % fqdn3.lower(), result=dict( - dn=lambda x: DN(x) == host3dn, + dn=host3dn, fqdn=[fqdn3.lower()], description=[u'Test host 3'], l=[u'Undisclosed location 3'], @@ -175,7 +175,7 @@ class test_service(Declarative): value=service1, summary=u'Added service "%s"' % service1, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], objectclass=objectclasses.service, ipauniqueid=[fuzzy_uuid], @@ -205,7 +205,7 @@ class test_service(Declarative): value=service1, summary=None, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], has_keytab=False, ipakrbauthzdata=[u'MS-PAC'], @@ -222,7 +222,7 @@ class test_service(Declarative): value=service1, summary=None, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbprincipalalias=[service1], objectclass=objectclasses.service, @@ -244,7 +244,7 @@ class test_service(Declarative): summary=u'1 service matched', result=[ dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], managedby_host=[fqdn1], ipakrbauthzdata=[u'MS-PAC'], @@ -264,7 +264,7 @@ class test_service(Declarative): summary=u'1 service matched', result=[ dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbprincipalalias=[service1], objectclass=objectclasses.service, @@ -285,7 +285,7 @@ class test_service(Declarative): failed=dict(managedby=dict(host=[(u'notfound', u'no such entry')])), completed=0, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbauthzdata=[u'MS-PAC'], managedby_host=[fqdn1], @@ -301,7 +301,7 @@ class test_service(Declarative): failed=dict(managedby=dict(host=[(u'notfound', u'This entry is not a member')])), completed=0, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbauthzdata=[u'MS-PAC'], managedby_host=[fqdn1], @@ -317,7 +317,7 @@ class test_service(Declarative): failed=dict(managedby=dict(host=[])), completed=1, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbauthzdata=[u'MS-PAC'], managedby_host=[fqdn1, fqdn2], @@ -333,7 +333,7 @@ class test_service(Declarative): failed=dict(managedby=dict(host=[])), completed=1, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbauthzdata=[u'MS-PAC'], managedby_host=[fqdn1], @@ -349,7 +349,7 @@ class test_service(Declarative): failed=dict(managedby=dict(host=[])), completed=1, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbauthzdata=[u'MS-PAC'], managedby_host=[fqdn1, fqdn3.lower()], @@ -365,7 +365,7 @@ class test_service(Declarative): failed=dict(managedby=dict(host=[])), completed=1, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, krbprincipalname=[service1], ipakrbauthzdata=[u'MS-PAC'], managedby_host=[fqdn1], @@ -396,8 +396,7 @@ class test_service(Declarative): managedby_host=[fqdn1], valid_not_before=fuzzy_date, valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), + subject=DN(('CN',api.env.host),x509.subject_base()), serial_number=fuzzy_digits, serial_number_hex=fuzzy_hex, md5_fingerprint=fuzzy_hash, @@ -415,7 +414,7 @@ class test_service(Declarative): value=service1, summary=None, result=dict( - dn=lambda x: DN(x) == service1dn, + dn=service1dn, usercertificate=[base64.b64decode(servercert)], krbprincipalname=[service1], has_keytab=False, @@ -425,8 +424,7 @@ class test_service(Declarative): # test case. valid_not_before=fuzzy_date, valid_not_after=fuzzy_date, - subject=lambda x: DN(x) == \ - DN(('CN',api.env.host),x509.subject_base()), + subject=DN(('CN',api.env.host),x509.subject_base()), serial_number=fuzzy_digits, serial_number_hex=fuzzy_hex, md5_fingerprint=fuzzy_hash, diff --git a/tests/test_xmlrpc/test_sudocmd_plugin.py b/tests/test_xmlrpc/test_sudocmd_plugin.py index 4574f774c..75b6bbccb 100644 --- a/tests/test_xmlrpc/test_sudocmd_plugin.py +++ b/tests/test_xmlrpc/test_sudocmd_plugin.py @@ -24,7 +24,7 @@ Test the `ipalib/plugins/sudocmd.py` module. from ipalib import api, errors from tests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid from tests.test_xmlrpc import objectclasses -from ipalib.dn import * +from ipapython.dn import DN sudocmd1 = u'/usr/bin/sudotestcmd1' @@ -72,9 +72,8 @@ class test_sudocmd(Declarative): value=sudocmd1, summary=u'Added Sudo Command "%s"' % sudocmd1, result=dict( - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), sudocmd=[sudocmd1], description=[u'Test sudo command 1'], objectclass=objectclasses.sudocmd, @@ -103,9 +102,8 @@ class test_sudocmd(Declarative): value=sudocmd1, summary=None, result=dict( - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), sudocmd=[sudocmd1], description=[u'Test sudo command 1'], ), @@ -122,9 +120,8 @@ class test_sudocmd(Declarative): summary=u'1 Sudo Command matched', result=[ dict( - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'), - ('cn','sudo'),api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'), + ('cn','sudo'),api.env.basedn), sudocmd=[sudocmd1], description=[u'Test sudo command 1'], ), @@ -155,9 +152,8 @@ class test_sudocmd(Declarative): value=sudocmd1, summary=None, result=dict( - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), sudocmd=[sudocmd1], description=[u'Updated sudo command 1'], ), diff --git a/tests/test_xmlrpc/test_sudocmdgroup_plugin.py b/tests/test_xmlrpc/test_sudocmdgroup_plugin.py index 2bfbb6259..b8c15737d 100644 --- a/tests/test_xmlrpc/test_sudocmdgroup_plugin.py +++ b/tests/test_xmlrpc/test_sudocmdgroup_plugin.py @@ -23,7 +23,7 @@ Test the `ipalib/plugins/sudocmdgroup.py` module. from ipalib import api, errors from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid -from ipalib.dn import * +from ipapython.dn import DN sudocmdgroup1 = u'testsudocmdgroup1' sudocmdgroup2 = u'testsudocmdgroup2' @@ -45,9 +45,8 @@ def create_command(sudocmd): sudocmd=[sudocmd], ipauniqueid=[fuzzy_uuid], description=[u'Test sudo command'], - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), ), ), ) @@ -77,9 +76,8 @@ class test_sudocmdgroup(Declarative): sudocmd=[u'/usr/bin/sudotestcmd1'], ipauniqueid=[fuzzy_uuid], description=[u'Test sudo command 1'], - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), ), ), ), @@ -94,9 +92,8 @@ class test_sudocmdgroup(Declarative): result=dict( sudocmd=[sudocmd1], description=[u'Test sudo command 1'], - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), ), ), ), @@ -143,9 +140,8 @@ class test_sudocmdgroup(Declarative): description=[u'Test desc 1'], objectclass=objectclasses.sudocmdgroup, ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), ), ), ), @@ -171,9 +167,8 @@ class test_sudocmdgroup(Declarative): result=dict( cn=[sudocmdgroup1], description=[u'Test desc 1'], - dn=lambda x: DN(x) == \ - DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), ), ), ), @@ -204,9 +199,8 @@ class test_sudocmdgroup(Declarative): result=dict( cn=[sudocmdgroup1], description=[u'New desc 1'], - dn=lambda x: DN(x) == \ - DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), ), summary=None, ), @@ -221,9 +215,8 @@ class test_sudocmdgroup(Declarative): truncated=False, result=[ dict( - dn=lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), cn=[sudocmdgroup1], description=[u'New desc 1'], ), @@ -275,9 +268,8 @@ class test_sudocmdgroup(Declarative): description=[u'Test desc 2'], objectclass=objectclasses.sudocmdgroup, ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), ), ), ), @@ -304,9 +296,8 @@ class test_sudocmdgroup(Declarative): result=dict( cn=[sudocmdgroup2], description=[u'Test desc 2'], - dn=lambda x: DN(x) == \ - DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), ), ), ), @@ -337,9 +328,8 @@ class test_sudocmdgroup(Declarative): result=dict( cn=[sudocmdgroup2], description=[u'New desc 2'], - dn=lambda x: DN(x) == \ - DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), ), summary=None, ), @@ -354,9 +344,8 @@ class test_sudocmdgroup(Declarative): truncated=False, result=[ dict( - dn=lambda x: DN(x) == \ - DN(('cn',sudocmdgroup2),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn',sudocmdgroup2),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), cn=[sudocmdgroup2], description=[u'New desc 2'], ), @@ -375,16 +364,14 @@ class test_sudocmdgroup(Declarative): truncated=False, result=[ dict( - dn=lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), cn=[sudocmdgroup1], description=[u'New desc 1'], ), dict( - dn=lambda x: DN(x) == \ - DN(('cn',sudocmdgroup2),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + dn=DN(('cn',sudocmdgroup2),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), cn=[sudocmdgroup2], description=[u'New desc 2'], ), @@ -410,9 +397,8 @@ class test_sudocmdgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), 'member_sudocmd': (sudocmd1,), 'cn': [sudocmdgroup1], 'description': [u'New desc 1'], @@ -427,9 +413,8 @@ class test_sudocmdgroup(Declarative): value=sudocmd1, summary=None, result=dict( - dn=lambda x: DN(x) == \ - DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), - api.env.basedn), + dn=DN(('sudocmd',sudocmd1),('cn','sudocmds'),('cn','sudo'), + api.env.basedn), sudocmd=[sudocmd1], description=[u'Test sudo command 1'], memberof_sudocmdgroup = [u'testsudocmdgroup1'], @@ -451,9 +436,8 @@ class test_sudocmdgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), 'member_sudocmd': (u'/usr/bin/sudotestcmd1',), 'cn': [sudocmdgroup1], 'description': [u'New desc 1'], @@ -474,9 +458,8 @@ class test_sudocmdgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), 'cn': [sudocmdgroup1], 'description': [u'New desc 1'], }, @@ -497,9 +480,8 @@ class test_sudocmdgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), 'cn': [sudocmdgroup1], 'description': [u'New desc 1'], }, @@ -523,9 +505,8 @@ class test_sudocmdgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), 'member_sudocmd': (sudocmd_plus,), 'cn': [sudocmdgroup1], 'description': [u'New desc 1'], @@ -546,9 +527,8 @@ class test_sudocmdgroup(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), - ('cn','sudo'),api.env.basedn), + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), 'cn': [sudocmdgroup1], 'description': [u'New desc 1'], }, diff --git a/tests/test_xmlrpc/test_user_plugin.py b/tests/test_xmlrpc/test_user_plugin.py index b257a90a3..4a4c69c19 100644 --- a/tests/test_xmlrpc/test_user_plugin.py +++ b/tests/test_xmlrpc/test_user_plugin.py @@ -27,7 +27,7 @@ from ipalib import api, errors from tests.test_xmlrpc import objectclasses from tests.util import assert_equal, assert_not_equal from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_password, fuzzy_string, fuzzy_dergeneralizedtime -from ipalib.dn import * +from ipapython.dn import DN user1=u'tuser1' user2=u'tuser2' @@ -52,7 +52,7 @@ def not_upg_check(response): class test_user(Declarative): cleanup_commands = [ - ('user_del', [user1, user2], {}), + ('user_del', [user1, user2, renameduser1], {}), ('group_del', [group1], {}), ] @@ -81,7 +81,7 @@ class test_user(Declarative): dict( desc='Try to rename non-existent %r' % user1, - command=('user_mod', [user1], dict(setattr=u'uid=tuser')), + command=('user_mod', [user1], dict(setattr=u'uid=%s' % renameduser1)), expected=errors.NotFound(reason=u'%s: user not found' % user1), ), @@ -109,18 +109,15 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), extra_check = upg_check, @@ -144,9 +141,8 @@ class test_user(Declarative): ), expected=dict( result=dict( - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), givenname=[u'Test'], homedirectory=[u'/home/tuser1'], loginshell=[u'/bin/sh'], @@ -173,9 +169,8 @@ class test_user(Declarative): expected=dict( result=[ { - 'dn': lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'), - ('cn','accounts'),api.env.basedn), + 'dn': DN(('uid','tuser1'),('cn','users'), + ('cn','accounts'),api.env.basedn), 'cn': [u'Test User1'], 'gecos': [u'Test User1'], 'givenname': [u'Test'], @@ -189,12 +184,10 @@ class test_user(Declarative): 'uidnumber': [fuzzy_digits], 'gidnumber': [fuzzy_digits], 'ipauniqueid': [fuzzy_uuid], - 'mepmanagedentry': lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], - 'krbpwdpolicyreference': lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], + 'mepmanagedentry': [DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], + 'krbpwdpolicyreference': [DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], 'nsaccountlock': False, 'has_keytab': False, 'has_password': False, @@ -217,9 +210,8 @@ class test_user(Declarative): expected=dict( result=[ { - 'dn':lambda x: DN(x) == \ - DN(('uid',user1),('cn','users'), - ('cn','accounts'),api.env.basedn), + 'dn':DN(('uid',user1),('cn','users'), + ('cn','accounts'),api.env.basedn), 'uid': [user1], }, ], @@ -237,9 +229,8 @@ class test_user(Declarative): expected=dict( result=[ dict( - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'), - ('cn','accounts'),api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'), + ('cn','accounts'),api.env.basedn), givenname=[u'Test'], homedirectory=[u'/home/tuser1'], loginshell=[u'/bin/sh'], @@ -267,9 +258,8 @@ class test_user(Declarative): expected=dict( result=[ dict( - dn=lambda x: DN(x) == \ - DN(('uid','admin'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','admin'),('cn','users'),('cn','accounts'), + api.env.basedn), homedirectory=[u'/home/admin'], loginshell=[u'/bin/bash'], sn=[u'Administrator'], @@ -281,9 +271,8 @@ class test_user(Declarative): gidnumber=[fuzzy_digits], ), dict( - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'), - ('cn','accounts'),api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'), + ('cn','accounts'),api.env.basedn), givenname=[u'Test'], homedirectory=[u'/home/tuser1'], loginshell=[u'/bin/sh'], @@ -311,9 +300,8 @@ class test_user(Declarative): expected=dict( result=[ dict( - dn=lambda x: DN(x) == \ - DN(('uid','admin'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','admin'),('cn','users'),('cn','accounts'), + api.env.basedn), homedirectory=[u'/home/admin'], loginshell=[u'/bin/bash'], sn=[u'Administrator'], @@ -465,9 +453,8 @@ class test_user(Declarative): command=('user_show', [user1], {}), expected=dict( result=dict( - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), givenname=[u'Finkle'], homedirectory=[u'/home/tuser1'], loginshell=[u'/bin/sh'], @@ -591,18 +578,15 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), extra_check = upg_check, @@ -632,18 +616,15 @@ class test_user(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user2),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user2),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser2'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser2'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), extra_check = upg_check, @@ -783,9 +764,8 @@ class test_user(Declarative): gidnumber=[fuzzy_digits], objectclass=objectclasses.group + [u'posixgroup'], ipauniqueid=[fuzzy_uuid], - dn=lambda x: DN(x) == \ - DN(('cn',group1),('cn','groups'),('cn','accounts'), - api.env.basedn), + dn=DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -830,18 +810,15 @@ class test_user(Declarative): postalcode=[u'01234-5678'], telephonenumber=[u'410-555-1212'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -880,12 +857,10 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=True, has_password=True, @@ -893,9 +868,8 @@ class test_user(Declarative): krbextradata=[fuzzy_string], krbpasswordexpiration=[fuzzy_dergeneralizedtime], krblastpwdchange=[fuzzy_dergeneralizedtime], - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -933,18 +907,15 @@ class test_user(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user2),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user2),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser2'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser2'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -1008,18 +979,15 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -1085,18 +1053,15 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -1151,18 +1116,15 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], - mepmanagedentry=lambda x: [DN(i) for i in x] == \ - [DN(('cn',user1),('cn','groups'),('cn','accounts'), - api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -1217,15 +1179,13 @@ class test_user(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], memberof_group=[u'ipausers'], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser2'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser2'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -1272,15 +1232,13 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], memberof_group=[group1], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser1'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), extra_check = not_upg_check, @@ -1310,15 +1268,13 @@ class test_user(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], - krbpwdpolicyreference=lambda x: [DN(i) for i in x] == \ - [DN(('cn','global_policy'),('cn',api.env.realm), - ('cn','kerberos'),api.env.basedn)], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], memberof_group=[group1], has_keytab=False, has_password=False, - dn=lambda x: DN(x) == \ - DN(('uid','tuser2'),('cn','users'),('cn','accounts'), - api.env.basedn), + dn=DN(('uid','tuser2'),('cn','users'),('cn','accounts'), + api.env.basedn), ), ), ), @@ -1350,9 +1306,8 @@ class test_user(Declarative): ), ), result={ - 'dn': lambda x: DN(x) == \ - DN(('cn', 'admins'), ('cn', 'groups'), - ('cn', 'accounts'), api.env.basedn), + 'dn': DN(('cn', 'admins'), ('cn', 'groups'), + ('cn', 'accounts'), api.env.basedn), 'member_user': [u'admin', user2], 'gidnumber': [fuzzy_digits], 'cn': [u'admins'], diff --git a/tests/test_xmlrpc/xmlrpc_test.py b/tests/test_xmlrpc/xmlrpc_test.py index cfba35fc5..7c32be0db 100644 --- a/tests/test_xmlrpc/xmlrpc_test.py +++ b/tests/test_xmlrpc/xmlrpc_test.py @@ -40,9 +40,9 @@ fuzzy_uuid = Fuzzy( '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' ) -# Matches netgroup dn +# Matches netgroup dn. Note (?i) at the beginning of the regexp is the ingnore case flag fuzzy_netgroupdn = Fuzzy( - '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 + '(?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 ) # Matches a hash signature, not enforcing length diff --git a/tests/util.py b/tests/util.py index 54a8dd14f..ed62a223a 100644 --- a/tests/util.py +++ b/tests/util.py @@ -30,6 +30,7 @@ import re import ipalib from ipalib.plugable import Plugin from ipalib.request import context +from ipapython.dn import DN class TempDir(object): def __init__(self): @@ -303,6 +304,9 @@ def assert_deepequal(expected, got, doc='', stack=tuple()): expected = list(expected) if isinstance(got, tuple): got = list(got) + if isinstance(expected, DN): + if isinstance(got, basestring): + got = DN(got) if not (isinstance(expected, Fuzzy) or callable(expected) or type(expected) is type(got)): raise AssertionError( TYPE % (doc, type(expected), type(got), expected, got, stack) -- cgit