Current objectclass updates in a form of "replace" update instruction
dependent on exact match of the old object class specification in the
update instruction and the real value in LDAP. However, this approach is
very error prone as object class definition can easily differ as for
example because of unexpected X-ORIGIN value. Such objectclass update
failures may lead to serious malfunctions later.

Add new update instruction type "replaceoc" with the following format:
replaceoc:OID:new
This update instruction will always replace an objectclass with
specified OID with the new definition.

https://fedorahosted.org/freeipa/ticket/2440
From ec04c7174c77c35613623341637e6e1c29e7e114 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 29 Aug 2012 14:04:48 +0200
Subject: [PATCH] Add safe updates for objectClasses

Current objectclass updates in a form of "replace" update instruction
dependent on exact match of the old object class specification in the
update instruction and the real value in LDAP. However, this approach is
very error prone as object class definition can easily differ as for
example because of unexpected X-ORIGIN value. Such objectclass update
failures may lead to serious malfunctions later.

Add new update instruction type "replaceoc" with the following format:
replaceoc:OID:new
This update instruction will always replace an objectclass with
specified OID with the new definition.

https://fedorahosted.org/freeipa/ticket/2440
---
 install/tools/man/ipa-ldap-updater.1     |  1 +
 install/updates/10-60basev3.update       |  2 +-
 install/updates/10-bind-schema.update    |  2 +-
 install/updates/10-selinuxusermap.update |  3 +--
 install/updates/10-sudo.update           |  4 ++--
 install/updates/60-trusts.update         |  2 +-
 ipaserver/install/ldapupdate.py          | 40 +++++++++++++++++++++++++++++++-
 7 files changed, 46 insertions(+), 8 deletions(-)

diff --git a/install/tools/man/ipa-ldap-updater.1 b/install/tools/man/ipa-ldap-updater.1
index df8dfe6503401302719aeb0baa7be5917cb75afa..aa13f5e2ee1abf81ddd5fd5f31de86092fd3289e 100644
--- a/install/tools/man/ipa-ldap-updater.1
+++ b/install/tools/man/ipa-ldap-updater.1
@@ -39,6 +39,7 @@ There are 7 keywords:
     * only: set an attribute to this
     * deleteentry: remove the entry
     * replace: replace an existing value, format is old: new
+    * replaceoc: replace an objectclass with specified OID with the new definition, format is replaceoc:OID:new
     * addifnew: add a new attribute and value only if the attribute doesn't already exist. Only works with single\-value attributes.
     * addifexist: add a new attribute and value only if the entry exists. This is used to update optional entries.
 
diff --git a/install/updates/10-60basev3.update b/install/updates/10-60basev3.update
index dbd68581e7321b3d544a918bc8154e6f2ecda946..e5c534e617a70801805003bf1abb3e03cf8eca52 100644
--- a/install/updates/10-60basev3.update
+++ b/install/updates/10-60basev3.update
@@ -7,4 +7,4 @@ add:objectClasses: (2.16.840.1.113730.3.8.12.7 NAME 'ipaKrb5DelegationACL' SUP g
 add:attributeTypes: (2.16.840.1.113730.3.8.11.32 NAME 'ipaKrbPrincipalAlias' DESC 'IPA principal alias' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
 add:attributeTypes: (2.16.840.1.113730.3.8.11.37 NAME 'ipaKrbAuthzData' DESC 'type of PAC preferred by a service' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3')
 add:objectClasses: (2.16.840.1.113730.3.8.12.8 NAME 'ipaKrbPrincipal' SUP krbPrincipalAux AUXILIARY MUST ( krbPrincipalName $$ ipaKrbPrincipalAlias ) X-ORIGIN 'IPA v3' )
-replace:objectClasses: ( 2.16.840.1.113730.3.8.4.2 NAME 'ipaService' DESC 'IPA service objectclass' AUXILIARY MAY ( memberOf $$ managedBy ) X-ORIGIN 'IPA v2' )::( 2.16.840.1.113730.3.8.4.2 NAME 'ipaService' DESC 'IPA service objectclass' AUXILIARY MAY ( memberOf $$ managedBy $$ ipaKrbAuthzData) X-ORIGIN 'IPA v2' )
+replaceoc:2.16.840.1.113730.3.8.4.2:( 2.16.840.1.113730.3.8.4.2 NAME 'ipaService' DESC 'IPA service objectclass' AUXILIARY MAY ( memberOf $$ managedBy $$ ipaKrbAuthzData) X-ORIGIN 'IPA v2' )
diff --git a/install/updates/10-bind-schema.update b/install/updates/10-bind-schema.update
index 0edbad2046929210e8b88d8232d24b5298912eb5..ae80dfafe93e994202e65b00950cd771e7416d2f 100644
--- a/install/updates/10-bind-schema.update
+++ b/install/updates/10-bind-schema.update
@@ -75,4 +75,4 @@ add:objectClasses:
       MUST idnsName
       MAY managedBy
       X-ORIGIN 'IPA v3' )
-replace:objectClasses:( 2.16.840.1.113730.3.8.6.1 NAME 'idnsZone' DESC 'Zone class' SUP idnsRecord STRUCTURAL MUST ( idnsZoneActive $$ idnsSOAmName $$ idnsSOArName $$ idnsSOAserial $$ idnsSOArefresh $$ idnsSOAretry $$ idnsSOAexpire $$ idnsSOAminimum ) MAY idnsUpdatePolicy )::( 2.16.840.1.113730.3.8.6.1 NAME 'idnsZone' DESC 'Zone class' SUP idnsRecord    STRUCTURAL MUST ( idnsName $$ idnsZoneActive $$ idnsSOAmName $$ idnsSOArName $$ idnsSOAserial $$ idnsSOArefresh $$ idnsSOAretry $$ idnsSOAexpire $$ idnsSOAminimum ) MAY ( idnsUpdatePolicy $$ idnsAllowQuery $$ idnsAllowTransfer $$ idnsAllowSyncPTR $$ idnsForwardPolicy $$ idnsForwarders ) )
+replaceoc:2.16.840.1.113730.3.8.6.1:( 2.16.840.1.113730.3.8.6.1 NAME 'idnsZone' DESC 'Zone class' SUP idnsRecord STRUCTURAL MUST ( idnsName $$ idnsZoneActive $$ idnsSOAmName $$ idnsSOArName $$ idnsSOAserial $$ idnsSOArefresh $$ idnsSOAretry $$ idnsSOAexpire $$ idnsSOAminimum ) MAY ( idnsUpdatePolicy $$ idnsAllowQuery $$ idnsAllowTransfer $$ idnsAllowSyncPTR $$ idnsForwardPolicy $$ idnsForwarders ) )
diff --git a/install/updates/10-selinuxusermap.update b/install/updates/10-selinuxusermap.update
index f9af01fadb219094ce4a748b417cd25635d1774e..75c027964c9ad7554a12a5f86ef943dc0ea0a1ce 100644
--- a/install/updates/10-selinuxusermap.update
+++ b/install/updates/10-selinuxusermap.update
@@ -19,12 +19,11 @@ add:attributeTypes:
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
      X-ORIGIN 'IPA v3')
      X-ORIGIN 'IPA v3')
-replace:objectClasses:( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $$ ipaGroupSearchFields $$ ipaSearchTimeLimit $$ ipaSearchRecordsLimit $$ ipaCustomFields $$ ipaHomesRootDir $$ ipaDefaultLoginShell $$ ipaDefaultPrimaryGroup $$ ipaMaxUsernameLength $$ ipaPwdExpAdvNotify $$ ipaUserObjectClasses $$ ipaGroupObjectClasses $$ ipaDefaultEmailDomain $$ ipaMigrationEnabled $$ ipaCertificateSubjectBase ) )::( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $$ ipaGroupSearchFields $$ ipaSearchTimeLimit $$ ipaSearchRecordsLimit $$ ipaCustomFields $$ ipaHomesRootDir $$ ipaDefaultLoginShell $$ ipaDefaultPrimaryGroup $$ ipaMaxUsernameLength $$ ipaPwdExpAdvNotify $$ ipaUserObjectClasses $$ ipaGroupObjectClasses $$ ipaDefaultEmailDomain $$ ipaMigrationEnabled $$ ipaCertificateSubjectBase $$ ipaSELinuxUserMapDefault $$ ipaSELinuxUserMapOrder) )
 
 # Add the default PAC service type relies on the new SELinux user map
 # values being there so add it here.
 dn: cn=schema
-replace:objectClasses:( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $$ ipaGroupSearchFields $$ ipaSearchTimeLimit $$ ipaSearchRecordsLimit $$ ipaCustomFields $$ ipaHomesRootDir $$ ipaDefaultLoginShell $$ ipaDefaultPrimaryGroup $$ ipaMaxUsernameLength $$ ipaPwdExpAdvNotify $$ ipaUserObjectClasses $$ ipaGroupObjectClasses $$ ipaDefaultEmailDomain $$ ipaMigrationEnabled $$ ipaCertificateSubjectBase $$ ipaSELinuxUserMapDefault $$ ipaSELinuxUserMapOrder ) )::( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $$ ipaGroupSearchFields $$ ipaSearchTimeLimit $$ ipaSearchRecordsLimit $$ ipaCustomFields $$ ipaHomesRootDir $$ ipaDefaultLoginShell $$ ipaDefaultPrimaryGroup $$ ipaMaxUsernameLength $$ ipaPwdExpAdvNotify $$ ipaUserObjectClasses $$ ipaGroupObjectClasses $$ ipaDefaultEmailDomain $$ ipaMigrationEnabled $$ ipaCertificateSubjectBase $$ ipaSELinuxUserMapDefault $$ ipaSELinuxUserMapOrder $$ ipaKrbAuthzData) )
+replaceoc:2.16.840.1.113730.3.8.2.1:( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $$ ipaGroupSearchFields $$ ipaSearchTimeLimit $$ ipaSearchRecordsLimit $$ ipaCustomFields $$ ipaHomesRootDir $$ ipaDefaultLoginShell $$ ipaDefaultPrimaryGroup $$ ipaMaxUsernameLength $$ ipaPwdExpAdvNotify $$ ipaUserObjectClasses $$ ipaGroupObjectClasses $$ ipaDefaultEmailDomain $$ ipaMigrationEnabled $$ ipaCertificateSubjectBase $$ ipaSELinuxUserMapDefault $$ ipaSELinuxUserMapOrder $$ ipaKrbAuthzData) )
 
 # Add the SELinux User map schema
 add:attributeTypes:
diff --git a/install/updates/10-sudo.update b/install/updates/10-sudo.update
index a12da00434cee1b49147586f54139ecc12d99e64..1c3615e44292cb57479fced5ef530ea8126a227f 100644
--- a/install/updates/10-sudo.update
+++ b/install/updates/10-sudo.update
@@ -37,6 +37,6 @@ add:attributeTypes: ( 1.3.6.1.4.1.15953.9.1.10
      ORDERING integerOrderingMatch
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
      X-ORIGIN 'SUDO' )
-replace:objectClasses:( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' DESC 'Sudoer Entries' STRUCTURAL MUST cn MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoOption $$ description ) X-ORIGIN 'SUDO' )::( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoRunAsUser $$ sudoRunAsGroup $$ sudoOption $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder $$ description ) X-ORIGIN 'SUDO')
+replaceoc:1.3.6.1.4.1.15953.9.2.1:( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoRunAsUser $$ sudoRunAsGroup $$ sudoOption $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder $$ description ) X-ORIGIN 'SUDO')
 
-replace:objectClasses: ( 2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory ) X-ORIGIN 'IPA v2' )::(2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder) X-ORIGIN 'IPA v2' )
+replaceoc:2.16.840.1.113730.3.8.8.1:( 2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder) X-ORIGIN 'IPA v2' )
diff --git a/install/updates/60-trusts.update b/install/updates/60-trusts.update
index cc9a771df901a90b457357c570dc06d34c0db4c8..2450d3b24cf60a30abb8e6f76aa9f2b0361495a6 100644
--- a/install/updates/60-trusts.update
+++ b/install/updates/60-trusts.update
@@ -21,7 +21,7 @@ add:attributeTypes: ( 2.16.840.1.113730.3.8.11.19 NAME 'ipaNTSupportedEncryption
 add:objectClasses: (2.16.840.1.113730.3.8.12.2 NAME 'ipaNTUserAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) MAY ( ipaNTHash $$ ipaNTLogonScript $$ ipaNTProfilePath $$ ipaNTHomeDirectory $$ ipaNTHomeDirectoryDrive ) X-ORIGIN 'IPA v3' )
 add:objectClasses: (2.16.840.1.113730.3.8.12.3 NAME 'ipaNTGroupAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) X-ORIGIN 'IPA v3' )
 add:objectClasses: (2.16.840.1.113730.3.8.12.4 NAME 'ipaNTDomainAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier $$ ipaNTFlatName $$ ipaNTDomainGUID ) MAY ( ipaNTFallbackPrimaryGroup ) X-ORIGIN 'IPA v3' )
-replace:objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTSecurityIdentifier $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )::objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTTrustedDomainSID $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )
+replaceoc:2.16.840.1.113730.3.8.12.5:( 2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTTrustedDomainSID $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )
 add:objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTTrustedDomainSID $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )
 
 dn: cn=trust admins,cn=groups,cn=accounts,$SUFFIX
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index d673ad57dd4abef68fefb256d933dd6341e6c847..b63592f49c146f1f52045f38d1e96953cba9674b 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -45,6 +45,7 @@ import pwd
 import fnmatch
 import csv
 import inspect
+import re
 from ipaserver.install.plugins import PRE_UPDATE, POST_UPDATE
 from ipaserver.install.plugins import FIRST, MIDDLE, LAST
 
@@ -58,7 +59,7 @@ class BadSyntax(installutils.ScriptError):
         return repr(self.value)
 
 class LDAPUpdate:
-    action_keywords = ["default", "add", "remove", "only", "deleteentry", "replace", "addifnew", "addifexist"]
+    action_keywords = ["default", "add", "remove", "only", "deleteentry", "replace", "replaceoc", "addifnew", "addifexist"]
 
     def __init__(self, dm_password, sub_dict={}, live_run=True,
                  online=True, ldapi=False, plugins=False):
@@ -535,6 +536,10 @@ class LDAPUpdate:
             (action, attr, update_values) = update.split(':',2)
             update_values = self._parse_values(update_values)
 
+            if action == "replaceoc":
+                oid = attr
+                attr = "objectClasses"
+
             # 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):
@@ -546,6 +551,20 @@ class LDAPUpdate:
                     entry_values = []
                 else:
                     entry_values = [entry_values]
+
+            if action == "replaceoc":
+                oid_index = {}
+                regex_oid = re.compile(r'^\s*\(\s+(?P<oid>\d+(\.\d+)*)\s+.*\)\s*$')
+                # build the OID index for replacing
+                for objectclass in entry_values:
+                    m = regex_oid.match(objectclass)
+                    if m is None:
+                        continue
+                    # In a corner case, there may be more representations of
+                    # the same objectclass due to the previous updates
+                    # We want to replace them all
+                    oid_index.setdefault(m.group('oid'), []).append(objectclass)
+
             for update_value in update_values:
                 if action == 'remove':
                     self.debug("remove: '%s' from %s, current value %s", update_value, attr, entry_values)
@@ -607,6 +626,25 @@ class LDAPUpdate:
                         entry.setValues(attr, entry_values)
                     except ValueError:
                         self.debug('replace: %s not found, skipping', old)
+                elif action == 'replaceoc':
+                    # replace objectclass
+                    try:
+                        old_values = oid_index[oid]
+                    except KeyError:
+                        # no value found, skip the update
+                        self.warning("replaceoc: OID %s not found", oid)
+                        continue
+                    try:
+                        for objectclass in old_values:
+                            entry_values.remove(objectclass)
+                    except ValueError:
+                        self.error('replaceoc: cannot replace objectclass, '
+                            'objectclass "%s" not found', objectclass)
+                        continue
+                    self.debug('replaceoc: replaced objectclass with OID %s with "%s"',
+                            oid, update_value)
+                    entry_values.append(update_value)
+                    entry.setValues(attr, entry_values)
 
         return entry
 
-- 
1.7.11.4

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

Reply via email to