Imported Upstream version 4.0.5

This commit is contained in:
Mario Fetka
2021-07-25 07:50:50 +02:00
parent 8ff3be4216
commit 3bfaa6e020
2049 changed files with 317193 additions and 1632423 deletions

View File

@@ -17,73 +17,30 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import pprint
import ldap.schema
import krbV
import ipapython.version
from ipalib import api
from ipapython.ipa_log_manager import log_mgr
from ipapython.dn import DN
from ipaserver.install.ldapupdate import connect
from ipaserver.install import installutils
SCHEMA_ELEMENT_CLASSES = (
SCHEMA_ELEMENT_CLASSES = {
# All schema model classes this tool can modify
# Depends on order, attributes first, then objectclasses
('attributetypes', ldap.schema.models.AttributeType),
('objectclasses', ldap.schema.models.ObjectClass),
)
'objectclasses': ldap.schema.models.ObjectClass,
'attributetypes': ldap.schema.models.AttributeType,
}
ORIGIN = 'IPA v%s' % ipapython.version.VERSION
logger = logging.getLogger(__name__)
log = log_mgr.get_logger(__name__)
def _get_oid_dependency_order(schema, cls):
"""
Returns a ordered list of OIDs sets, in order which respects inheritance in LDAP
OIDs in second set, depend on first set, etc.
:return [set(1st-tree-level), set(2nd-tree-level), ...]
"""
top_node = '_'
ordered_oid_groups = []
tree = schema.tree(cls) # tree structure of schema
# remove top_node from tree, it breaks ordering
# we don't need this, tree from file is not consistent
del tree[top_node]
unordered_oids = set(tree.keys())
# split into two groups, parents and child nodes, and iterate until
# child nodes are not empty
while unordered_oids:
parent_nodes = set()
child_nodes = set()
for node in unordered_oids:
if node not in child_nodes:
# if node was child once, must remain as child
parent_nodes.add(node)
for child_oid in tree[node]:
# iterate over all child nodes stored in tree[node] per node
# child node must be removed from parents
parent_nodes.discard(child_oid)
child_nodes.add(child_oid)
ordered_oid_groups.append(parent_nodes) # parents nodes are not dependent
assert len(child_nodes) < len(unordered_oids) # while iteration must be finite
unordered_oids = child_nodes # extract new parent nodes in next iteration
return ordered_oid_groups
def update_schema(schema_files, ldapi=False, dm_password=None,):
def update_schema(schema_files, ldapi=False, dm_password=None, live_run=True):
"""Update schema to match the given ldif files
Schema elements present in the LDIF files but missing from the DS schema
@@ -99,74 +56,71 @@ def update_schema(schema_files, ldapi=False, dm_password=None,):
:param schema_files: List of filenames to update from
:param ldapi: if true, use ldapi to connect
:param dm_password: directory manager password
:live_run: if false, changes will not be applied
:return:
True if modifications were made
(or *would be* made, for live_run=false)
"""
SCHEMA_ELEMENT_CLASSES_KEYS = [x[0] for x in SCHEMA_ELEMENT_CLASSES]
conn = connect(ldapi=ldapi, dm_password=dm_password,
realm=api.env.realm,
realm=krbV.default_context().default_realm,
fqdn=installutils.get_fqdn())
old_schema = conn.schema
schema_entry = conn.get_entry(DN(('cn', 'schema')),
SCHEMA_ELEMENT_CLASSES_KEYS)
SCHEMA_ELEMENT_CLASSES.keys())
modified = False
# The exact representation the DS gives us for each OID
# (for debug logging)
old_entries_by_oid = {cls(attr).oid: attr.decode('utf-8')
for (attrname, cls) in SCHEMA_ELEMENT_CLASSES
old_entries_by_oid = {cls(str(attr)).oid: str(attr)
for attrname, cls in SCHEMA_ELEMENT_CLASSES.items()
for attr in schema_entry[attrname]}
for filename in schema_files:
logger.debug('Processing schema LDIF file %s', filename)
url = "file://{}".format(filename)
_dn, new_schema = ldap.schema.subentry.urlfetch(url)
log.info('Processing schema LDIF file %s', filename)
dn, new_schema = ldap.schema.subentry.urlfetch(filename)
for attrname, cls in SCHEMA_ELEMENT_CLASSES:
for oids_set in _get_oid_dependency_order(new_schema, cls):
# Set of all elements of this class, as strings given by the DS
new_elements = []
for oid in oids_set:
new_obj = new_schema.get_obj(cls, oid)
old_obj = old_schema.get_obj(cls, oid)
# Compare python-ldap's sanitized string representations
# to see if the value is different
# This can give false positives, e.g. with case differences
# in case-insensitive names.
# But, false positives are harmless (and infrequent)
if not old_obj or str(new_obj) != str(old_obj):
# Note: An add will automatically replace any existing
# schema with the same OID. So, we only add.
value = add_x_origin(new_obj)
for attrname, cls in SCHEMA_ELEMENT_CLASSES.items():
if old_obj:
old_attr = old_entries_by_oid.get(oid)
logger.debug('Replace: %s', old_attr)
logger.debug(' with: %s', value)
else:
logger.debug('Add: %s', value)
# Set of all elements of this class, as strings given by the DS
new_elements = []
new_elements.append(value.encode('utf-8'))
for oid in new_schema.listall(cls):
new_obj = new_schema.get_obj(cls, oid)
old_obj = old_schema.get_obj(cls, oid)
# Compare python-ldap's sanitized string representations
# to see if the value is different
# This can give false positives, e.g. with case differences
# in case-insensitive names.
# But, false positives are harmless (and infrequent)
if not old_obj or str(new_obj) != str(old_obj):
# Note: An add will automatically replace any existing
# schema with the same OID. So, we only add.
value = add_x_origin(new_obj)
new_elements.append(value)
modified = modified or new_elements
schema_entry[attrname].extend(new_elements)
# we need to iterate schema updates, due to dependencies (SUP)
# schema_entry doesn't respect order of objectclasses/attributes
# so updates must be executed with groups of independent OIDs
if new_elements:
modlist = schema_entry.generate_modlist()
logger.debug("Schema modlist:\n%s",
pprint.pformat(modlist))
conn.update_entry(schema_entry)
if old_obj:
old_attr = old_entries_by_oid.get(oid)
log.info('Replace: %s', old_attr)
log.info(' with: %s', value)
else:
log.info('Add: %s', value)
if not modified:
logger.debug('Not updating schema')
modified = modified or new_elements
schema_entry[attrname].extend(new_elements)
# FIXME: We should have a better way to display the modlist,
# for now display raw output of our internal routine
modlist = schema_entry.generate_modlist()
log.debug("Complete schema modlist:\n%s", pprint.pformat(modlist))
if modified and live_run:
conn.update_entry(schema_entry)
else:
log.info('Not updating schema')
return modified