From 219fd4b145462204521dc021290d7b1c5f5cde1b Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Sep 18 2018 07:50:16 +0000 Subject: Issue 49928 - Refactor and improve schema CLI/lib389 part to DSLdapObject Description: First commit that refactors Schema object and removes SchemaLegacy usage from CLI. Add full CLI Schema functionality to lib389. It includes: list, query, add, edit, remove operations. https://pagure.io/389-ds-base/issue/49928 Reviewed by: mreynolds, wibrown (Thanks!) --- diff --git a/src/lib389/lib389/cli_conf/schema.py b/src/lib389/lib389/cli_conf/schema.py index 11dcc20..68962c3 100644 --- a/src/lib389/lib389/cli_conf/schema.py +++ b/src/lib389/lib389/cli_conf/schema.py @@ -10,67 +10,141 @@ from lib389.cli_base import _get_arg from lib389.schema import Schema -def list_attributetype(inst, basedn, log, args): +def _validate_dual_args(enable_arg, disable_arg): + if enable_arg and disable_arg: + raise ValueError('Only one of the flags should be specified: %s and %s' % (enable_arg, disable_arg)) + + if enable_arg: + return 1 + elif disable_arg: + return 0 + + +def list_attributetypes(inst, basedn, log, args): + log = log.getChild('list_attributetypes') + schema = Schema(inst) if args is not None and args.json: - print(inst.schema.get_attributetypes(json=True)) + print(schema.get_attributetypes(json=True)) else: - for attributetype in inst.schema.get_attributetypes(): - print(attributetype) + for attributetype in schema.get_attributetypes(): + log.info(attributetype) def list_objectclasses(inst, basedn, log, args): + log = log.getChild('list_objectclasses') + schema = Schema(inst) if args is not None and args.json: - print(inst.schema.get_objectclasses(json=True)) + print(schema.get_objectclasses(json=True)) else: - for oc in inst.schema.get_objectclasses(): - print(oc) + for oc in schema.get_objectclasses(): + log.info(oc) def list_matchingrules(inst, basedn, log, args): + log = log.getChild('list_matchingrules') + schema = Schema(inst) if args is not None and args.json: - print(inst.schema.get_matchingrules(json=True)) + print(schema.get_matchingrules(json=True)) else: - for mr in inst.schema.matchingrules(): - print(mr) + for mr in schema.get_matchingrules(): + log.info(mr) def query_attributetype(inst, basedn, log, args): + log = log.getChild('query_attributetype') + schema = Schema(inst) # Need the query type attr = _get_arg(args.attr, msg="Enter attribute to query") if args.json: - print(inst.schema.query_attributetype(attr, json=True)) + print(schema.query_attributetype(attr, json=args.json)) else: - attributetype, must, may = inst.schema.query_attributetype(attr) - print(attributetype) - print("") - print("MUST") + attributetype, must, may = schema.query_attributetype(attr, json=args.json) + log.info(attributetype) + log.info("") + log.info("MUST") for oc in must: - print(oc) - print("") - print("MAY") + log.info(oc) + log.info("") + log.info("MAY") for oc in may: - print(oc) + log.info(oc) def query_objectclass(inst, basedn, log, args): + log = log.getChild('query_objectclass') + schema = Schema(inst) # Need the query type oc = _get_arg(args.attr, msg="Enter objectclass to query") + result = schema.query_objectclass(oc, json=args.json) if args.json: - print(inst.schema.query_objectclass(oc, json=True)) + print(result) else: - print("Not done") + log.info(result) def query_matchingrule(inst, basedn, log, args): + log = log.getChild('query_matchingrule') + schema = Schema(inst) # Need the query type attr = _get_arg(args.attr, msg="Enter attribute to query") + result = schema.query_matchingrule(attr, json=args.json) if args.json: - print(inst.schema.query_matchingrule(attr, json=True)) + print(result) else: - print("Not done") + log.info(result) + + +def add_attributetype(inst, basedn, log, args): + log = log.getChild('add_attributetype') + schema = Schema(inst) + parameters = _get_parameters(args, 'attributetypes') + schema.add_attributetype(parameters) + log.info("Successfully added the attributeType") + + +def add_objectclass(inst, basedn, log, args): + log = log.getChild('add_objectclass') + schema = Schema(inst) + + parameters = _get_parameters(args, 'objectclasses') + schema.add_objectclass(parameters) + log.info("Successfully added the objectClass") + + +def edit_attributetype(inst, basedn, log, args): + log = log.getChild('edit_attributetype') + schema = Schema(inst) + parameters = _get_parameters(args, 'attributetypes') + schema.edit_attributetype(args.name, parameters) + log.info("Successfully changed the attributetype") + + +def remove_attributetype(inst, basedn, log, args): + log = log.getChild('remove_attributetype') + attr = _get_arg(args.name, msg="Enter attribute to remove") + schema = Schema(inst) + schema.remove_attributetype(attr) + log.info("Successfully removed the attributetype") + + +def edit_objectclass(inst, basedn, log, args): + log = log.getChild('edit_objectclass') + schema = Schema(inst) + parameters = _get_parameters(args, 'objectclasses') + schema.edit_objectclass(args.name, parameters) + log.info("Successfully changed the objectClass") + + +def remove_objectclass(inst, basedn, log, args): + log = log.getChild('remove_objectclass') + attr = _get_arg(args.name, msg="Enter objectClass to remove") + schema = Schema(inst) + schema.remove_objectclass(attr) + log.info("Successfully removed the objectClass") def reload_schema(inst, basedn, log, args): + log = log.getChild('reload_schema') schema = Schema(inst) log.info('Attempting to add task entry... This will fail if Schema Reload plug-in is not enabled.') task = schema.reload(args.schemadir) @@ -82,38 +156,152 @@ def reload_schema(inst, basedn, log, args): else: raise ValueError("Schema reload task failed, please check the errors log for more information") else: - log.info('Successfully added task entry ' + task.dn) + log.info('Successfully added task entry {}'.format(task.dn)) log.info("To verify that the schema reload operation was successful, please check the error logs.") +def _get_parameters(args, type): + if type not in ('attributetypes', 'objectclasses'): + raise ValueError("Wrong parser type: %s" % type) + + parameters = {'names': (args.name,), + 'oid': args.oid, + 'desc': args.desc, + 'obsolete': _validate_dual_args(args.obsolete, args.not_obsolete)} + + if type == 'attributetypes': + parameters.update({'single_value': _validate_dual_args(args.single_value, args.multi_value), + 'syntax': args.syntax, + 'syntax_len': None, # We need it for + 'x_ordered': None, # the correct ldap.schema.models work + 'no_user_mod': _validate_dual_args(args.no_user_mod, args.with_user_mod), + 'equality': args.equality, + 'substr': args.substr, + 'ordering': args.ordering, + 'x_origin': args.x_origin, + 'collective': _validate_dual_args(args.collective, args.not_collective), + 'usage': args.usage, + 'sup': args.sup}) + elif type == 'objectclasses': + parameters.update({'must': args.must, + 'may': args.may, + 'kind': args.kind, + 'sup': args.sup}) + + return parameters + + +def _add_parser_args(parser, type): + if type not in ('attributetypes', 'objectclasses'): + raise ValueError("Wrong parser type: %s" % type) + + parser.add_argument('name', help='NAME of the object') + parser.add_argument('--oid', help='OID assigned to the object') + parser.add_argument('--desc', help='Description text(DESC) of the object') + parser.add_argument('--obsolete', action='store_true', + help='True if the object is marked as OBSOLETE in the schema.' + 'Only one of the flags this or --not-obsolete should be specified') + parser.add_argument('--not-obsolete', action='store_true', + help='True if the OBSOLETE mark should be removed' + 'object is marked as OBSOLETE in the schema' + 'Only one of the flags this or --obsolete should be specified') + if type == 'attributetypes': + parser.add_argument('--syntax', required=True, + help='OID of the LDAP syntax assigned to the attribute') + parser.add_argument('--single-value', action='store_true', + help='True if the matching rule must have only one value' + 'Only one of the flags this or --multi-value should be specified') + parser.add_argument('--multi-value', action='store_true', + help='True if the matching rule may have multiple values (default)' + 'Only one of the flags this or --single-value should be specified') + parser.add_argument('--no-user-mod', action='store_true', + help='True if the attribute is not modifiable by a client application' + 'Only one of the flags this or --with-user-mod should be specified') + parser.add_argument('--with-user-mod', action='store_true', + help='True if the attribute is modifiable by a client application (default)' + 'Only one of the flags this or --no-user-mode should be specified') + parser.add_argument('--equality', + help='NAME or OID of the matching rule used for checking' + 'whether attribute values are equal') + parser.add_argument('--substr', + help='NAME or OID of the matching rule used for checking' + 'whether an attribute value contains another value') + parser.add_argument('--ordering', + help='NAME or OID of the matching rule used for checking' + 'whether attribute values are lesser - equal than') + parser.add_argument('--x-origin', + help='Provides information about where the attribute type is defined') + parser.add_argument('--collective', + help='True if the attribute is assigned their values by virtue in their membership in some collection' + 'Only one of the flags this or --not-collective should be specified') + parser.add_argument('--not-collective', + help='True if the attribute is not assigned their values by virtue in their membership in some collection (default)' + 'Only one of the flags this or --collective should be specified') + parser.add_argument('--usage', + help='The flag indicates how the attribute type is to be used.' + 'userApplications - user, directoryOperation - directory operational,' + 'distributedOperation - DSA-shared operational, dSAOperation - DSA - specific operational') + parser.add_argument('--sup', nargs='?', help='The list of NAMEs or OIDs of attribute types' + 'this attribute type is derived from') + elif type == 'objectclasses': + parser.add_argument('--must', nargs='+', help='NAMEs or OIDs of all attributes an entry of the object must have') + parser.add_argument('--may', nargs='+', help='NAMEs or OIDs of additional attributes an entry of the object may have') + parser.add_argument('--kind', help='Kind of an object. 0 = STRUCTURAL (default), 1 = ABSTRACT, 2 = AUXILIARY') + parser.add_argument('--sup', nargs='+', help='NAMEs or OIDs of object classes this object is derived from') + else: + raise ValueError("Wrong parser type: %s" % type) + + def create_parser(subparsers): schema_parser = subparsers.add_parser('schema', help='Query and manipulate schema') - subcommands = schema_parser.add_subparsers(help='schema') + schema_subcommands = schema_parser.add_subparsers(help='schema') - list_attributetype_parser = subcommands.add_parser('list_attributetype', help='List available attribute types on this system') - list_attributetype_parser.set_defaults(func=list_attributetype) + attributetypes_parser = schema_subcommands.add_parser('attributetypes', help='Work with attribute types on this system') + attributetypes_subcommands = attributetypes_parser.add_subparsers(help='schema') + at_list_parser = attributetypes_subcommands.add_parser('list', help='List available attribute types on this system') + at_list_parser.set_defaults(func=list_attributetypes) + at_query_parser = attributetypes_subcommands.add_parser('query', help='Query an attribute to determine object classes that may or must take it') + at_query_parser.set_defaults(func=query_attributetype) + at_query_parser.add_argument('attr', nargs='?', help='Attribute type to query') + at_add_parser = attributetypes_subcommands.add_parser('add', help='Add an attribute type to this system') + at_add_parser.set_defaults(func=add_attributetype) + _add_parser_args(at_add_parser, 'attributetypes') + at_edit_parser = attributetypes_subcommands.add_parser('edit', help='Edit an attribute type on this system') + at_edit_parser.set_defaults(func=edit_attributetype) + _add_parser_args(at_edit_parser, 'attributetypes') + at_remove_parser = attributetypes_subcommands.add_parser('remove', help='Remove an attribute type on this system') + at_remove_parser.set_defaults(func=remove_attributetype) + at_remove_parser.add_argument('name', help='NAME of the object') - query_attributetype_parser = subcommands.add_parser('query_attributetype', help='Query an attribute to determine object classes that may or must take it') - query_attributetype_parser.set_defaults(func=query_attributetype) - query_attributetype_parser.add_argument('attr', nargs='?', help='Attribute type to query') + objectclasses_parser = schema_subcommands.add_parser('objectclasses', help='Work with objectClasses on this system') + objectclasses_subcommands = objectclasses_parser.add_subparsers(help='schema') + oc_list_parser = objectclasses_subcommands.add_parser('list', help='List available objectClasses on this system') + oc_list_parser.set_defaults(func=list_objectclasses) + oc_query_parser = objectclasses_subcommands.add_parser('query', help='Query an objectClass') + oc_query_parser.set_defaults(func=query_objectclass) + oc_query_parser.add_argument('attr', nargs='?', help='ObjectClass to query') + oc_add_parser = objectclasses_subcommands.add_parser('add', help='Add an objectClass to this system') + oc_add_parser.set_defaults(func=add_objectclass) + _add_parser_args(oc_add_parser, 'objectclasses') + oc_edit_parser = objectclasses_subcommands.add_parser('edit', help='Edit an objectClass on this system') + oc_edit_parser.set_defaults(func=edit_objectclass) + _add_parser_args(oc_edit_parser, 'objectclasses') + oc_remove_parser = objectclasses_subcommands.add_parser('remove', help='Remove an objectClass on this system') + oc_remove_parser.set_defaults(func=remove_objectclass) + oc_remove_parser.add_argument('name', help='NAME of the object') - list_objectclass_parser = subcommands.add_parser('list_objectclasses', help='List available objectclasses on this system') - list_objectclass_parser.set_defaults(func=list_objectclasses) + matchingrules_parser = schema_subcommands.add_parser('matchingrules', help='Work with matching rules on this system') + matchingrules_subcommands = matchingrules_parser.add_subparsers(help='schema') + mr_list_parser = matchingrules_subcommands.add_parser('list', help='List available matching rules on this system') + mr_list_parser.set_defaults(func=list_matchingrules) + mr_query_parser = matchingrules_subcommands.add_parser('query', help='Query a matching rule') + mr_query_parser.set_defaults(func=query_matchingrule) + mr_query_parser.add_argument('attr', nargs='?', help='Matching rule to query') - query_objectclass_parser = subcommands.add_parser('query_objectclass', help='Query an objectclass') - query_objectclass_parser.set_defaults(func=query_objectclass) - query_objectclass_parser.add_argument('attr', nargs='?', help='Objectclass to query') - - reload_parser = subcommands.add_parser('reload', help='Dynamically reload schema while server is running') + reload_parser = schema_subcommands.add_parser('reload', help='Dynamically reload schema while server is running') reload_parser.set_defaults(func=reload_schema) reload_parser.add_argument('-d', '--schemadir', help="directory where schema files are located") reload_parser.add_argument('--wait', action='store_true', default=False, help="Wait for the reload task to complete") - list_matchingrules_parser = subcommands.add_parser('list_matchingrules', help='List available matching rules on this system') - list_matchingrules_parser.set_defaults(func=list_matchingrules) - - query_matchingrule_parser = subcommands.add_parser('query_matchingrule', help='Query a matchingrule') - query_matchingrule_parser.set_defaults(func=query_matchingrule) - query_matchingrule_parser.add_argument('attr', nargs='?', help='Matchingrule to query') diff --git a/src/lib389/lib389/schema.py b/src/lib389/lib389/schema.py index bd51a39..39f0de4 100755 --- a/src/lib389/lib389/schema.py +++ b/src/lib389/lib389/schema.py @@ -8,28 +8,150 @@ """ You will access this from: - DirSrv.schema.methodName() + schema = Schema(instance) """ import glob import ldap +import ldif from json import dumps as dump_json from operator import itemgetter from ldap.schema.models import AttributeType, ObjectClass, MatchingRule - from lib389._constants import * from lib389._constants import DN_SCHEMA from lib389.utils import ds_is_newer from lib389._mapped_object import DSLdapObject from lib389.tasks import SchemaReloadTask +OBJECT_MODEL_PARAMS = {ObjectClass: {'names': (), 'oid': None, 'desc': None, 'obsolete': 0, + 'kind': 0, 'sup': (), 'must': (), 'may': ()}, + AttributeType: {'names': (), 'oid': None, 'desc': None, 'obsolete': 0, + 'sup': (), 'equality': None, 'ordering': None, 'substr': None, + 'syntax': None, 'syntax_len': None, 'single_value': 0, 'collective': 0, + 'no_user_mod': 0, 'usage': 0, 'x_origin': None, 'x_ordered': None}} + class Schema(DSLdapObject): + """An object that represents the schema entry + + :param instance: An instance + :type instance: lib389.DirSrv + """ + def __init__(self, instance): super(Schema, self).__init__(instance=instance) self._dn = DN_SCHEMA self._rdn_attribute = 'cn' + @staticmethod + def _get_attr_name_by_model(object_model): + # Validate the model and return its attribute name + if object_model in (ObjectClass, AttributeType, MatchingRule): + return object_model.schema_attribute + else: + raise ValueError("Wrong object model was specified") + + def _get_schema_objects(self, object_model, json=False): + attr_name = self._get_attr_name_by_model(object_model) + + results = self.get_attr_vals_utf8(attr_name) + + if json: + object_insts = [vars(object_model(obj_i)) for obj_i in results] + + for obj_i in object_insts: + # Add normalized name for sorting. Some matching rules don't have a name + if len(obj_i["names"]) > 0: + obj_i['name'] = obj_i['names'][0].lower() + else: + obj_i['name'] = "" + object_insts = sorted(object_insts, key=itemgetter('name')) + result = {'type': 'list', 'items': object_insts} + + return dump_json(result) + else: + return [object_model(obj_i) for obj_i in results] + + def _get_schema_object(self, name, object_model, json=False): + objects = self._get_schema_objects(object_model, json=json) + schema_object = [obj_i for obj_i in objects if name.lower() in + list(map(str.lower, obj_i.names))] + + if len(schema_object) != 1: + # This is an error. + if json: + raise ValueError('Could not find: %s' % name) + else: + return None + + return schema_object[0] + + def _add_schema_object(self, parameters, object_model): + attr_name = self._get_attr_name_by_model(object_model) + + if len(parameters) == 0: + raise ValueError('Parameters should be specified') + + # Validate args + if "names" not in parameters.keys(): + raise ValueError('%s name should be specified' % attr_name) + for name in parameters["names"]: + schema_object_old = self._get_schema_object(name, object_model) + if schema_object_old is not None: + raise ValueError('The %s with the name %s already exists' % (attr_name, name)) + + # Default structure. We modify it later with the specified arguments + schema_object = object_model() + + for oc_param, value in parameters.items(): + if oc_param.lower() not in OBJECT_MODEL_PARAMS[object_model].keys(): + raise ValueError('Wrong parameter name was specified: %s' % oc_param) + if value is not None: + # ldap.schema.models requires tuple + if type(value) == list: + value = tuple(value) + setattr(schema_object, oc_param.lower(), value) + + # Set other not defined arguments so objectClass model work correctly + # all 'None', but OBSOLETE and KIND are '0' (STRUCTURAL) + # It is automatically assigned to 'SUP top' + parameters_none = {k.lower(): v for k, v in parameters.items() if v is None} + for k, v in parameters_none.items(): + setattr(schema_object, k, OBJECT_MODEL_PARAMS[object_model][k]) + return self.add(attr_name, str(schema_object)) + + def _remove_schema_object(self, name, object_model): + attr_name = self._get_attr_name_by_model(object_model) + schema_object = self._get_schema_object(name, object_model) + + return self.remove(attr_name, str(schema_object)) + + def _edit_schema_object(self, name, parameters, object_model): + attr_name = self._get_attr_name_by_model(object_model) + schema_object = self._get_schema_object(name, object_model) + schema_object_str_old = str(schema_object) + + if len(parameters) == 0: + raise ValueError('Parameters should be specified') + + for oc_param, value in parameters.items(): + if oc_param.lower() not in OBJECT_MODEL_PARAMS[object_model].keys(): + raise ValueError('Wrong parameter name was specified: %s' % oc_param) + if value is not None: + # ldap.schema.models requires tuple + if type(value) == list: + value = tuple(value) + setattr(schema_object, oc_param.lower(), value) + + schema_object_str = str(schema_object) + if schema_object_str == schema_object_str_old: + raise ValueError('ObjectClass is already in the required state. Nothing to change') + + self.remove(attr_name, schema_object_str_old) + return self.add(attr_name, schema_object_str) + def reload(self, schema_dir=None): + """Reload the schema""" + task = SchemaReloadTask(self._instance) task_properties = {} @@ -40,6 +162,234 @@ class Schema(DSLdapObject): return task + def list_files(self): + """Return a list of the schema files in the instance schemadir""" + + file_list = [] + file_list += glob.glob(os.path.join(self.conn.schemadir, "*.ldif")) + if ds_is_newer('1.3.6.0'): + file_list += glob.glob(os.path.join(self.conn.ds_paths.system_schema_dir, "*.ldif")) + return file_list + + def file_to_ldap(self, filename): + """Convert the given schema file name to its python-ldap format + suitable for passing to ldap.schema.SubSchema() + + :param filename: the full path and filename of a schema file in ldif format + :type filename: str + """ + + with open(filename, 'r') as f: + ldif_parser = ldif.LDIFRecordList(f, max_entries=1) + if not ldif_parser: + return None + ldif_parser.parse() + if not ldif_parser.all_records: + return None + return ldif_parser.all_records[0][1] + + def file_to_subschema(self, filename): + """Convert the given schema file name to its python-ldap format + ldap.schema.SubSchema object + + :param filename: the full path and filename of a schema file in ldif format + :type filename: str + """ + + ent = self.file_to_ldap(filename) + if not ent: + return None + return ldap.schema.SubSchema(ent) + + def get_schema_csn(self): + """Return the schema nsSchemaCSN attribute""" + + return self.get_attr_val_utf8('nsSchemaCSN') + + def add_attributetype(self, parameters): + """Add an attribute type definition to the schema. + + :param parameters: an attribute type definition to add + :type parameters: str + """ + + return self._add_schema_object(parameters, AttributeType) + + def add_objectclass(self, parameters): + """Add an object class definition to the schema. + + :param parameters: an objectClass definition to add + :type parameters: str + """ + + return self._add_schema_object(parameters, ObjectClass) + + def remove_attributetype(self, name): + """Remove the attribute type definition from the schema. + + :param name: the name of the attributeType you want to remove. + :type name: str + """ + + return self._remove_schema_object(name, AttributeType) + + def remove_objectclass(self, name): + """Remove an objectClass definition from the schema. + + :param name: the name of the objectClass you want to remove. + :type name: str + """ + + return self._remove_schema_object(name, ObjectClass) + + def edit_attributetype(self, name, parameters): + """Edit the attribute type definition in the schema + + :param name: the name of the attribute type you want to edit. + :type name: str + :param parameters: an attribute type definition to edit + :type parameters: str + """ + + return self._edit_schema_object(name, parameters, AttributeType) + + def edit_objectclass(self, name, parameters): + """Edit an objectClass definition in the schema. + + :param name: the name of the objectClass you want to edit. + :type name: str + :param parameters: an objectClass definition to edit + :type parameters: str + """ + + return self._edit_schema_object(name, parameters, ObjectClass) + + def get_objectclasses(self, json=False): + """Returns a list of ldap.schema.models.ObjectClass objects for all + objectClasses supported by this instance. + + :param json: dump the result into JSON format + :type json: bool + """ + + return self._get_schema_objects(ObjectClass, json=json) + + def get_attributetypes(self, json=False): + """Returns a list of ldap.schema.models.AttributeType objects for all + attributeTypes supported by this instance. + + :param json: dump the result into JSON format + :type json: bool + """ + + return self._get_schema_objects(AttributeType, json=json) + + def get_matchingrules(self, json=False): + """Return a list of the server defined matching rules + + :param json: dump the result into JSON format + :type json: bool + """ + + return self._get_schema_objects(MatchingRule, json=json) + + def query_matchingrule(self, mr_name, json=False): + """Returns a single matching rule instance that matches the mr_name. + Returns None if the matching rule doesn't exist. + + :param mr_name: the name of the matching rule you want to query. + :type mr_name: str + :param json: dump the result into JSON format + :type json: bool + + :returns: MatchingRule or None + + + """ + + matching_rule = self._get_schema_object(mr_name, MatchingRule, json=json) + + if json: + result = {'type': 'schema', 'mr': vars(matching_rule)} + return dump_json(result) + else: + return str(matching_rule) + + def query_objectclass(self, objectclassname, json=False): + """Returns a single ObjectClass instance that matches objectclassname. + Returns None if the objectClass doesn't exist. + + :param objectclassname: The name of the objectClass you want to query. + :type objectclassname: str + :param json: dump the result into JSON format + :type json: bool + + :returns: ObjectClass or None + + ex. query_objectclass('account') + + """ + + objectclass = self._get_schema_object(objectclassname, ObjectClass, json=json) + + if json: + result = {'type': 'schema', 'oc': vars(objectclass)} + return dump_json(result) + else: + return str(objectclass) + + def query_attributetype(self, attributetypename, json=False): + """Returns a tuple of the AttributeType, and what objectclasses may or + must take this attributeType. Returns None if attributetype doesn't + exist. + + :param attributetypename: The name of the attributeType you want to query + :type attributetypename: str + :param json: dump the result into JSON format + :type json: bool + + :returns: (AttributeType, Must, May) or None + + ex. query_attributetype('uid') + ( , + [, ...], + [, ...] ) + """ + + # First, get the attribute that matches name. We need to consider + # alternate names. There is no way to search this, so we have to + # filter our set of all attribute types. + attributetype = self._get_schema_object(attributetypename, AttributeType, json=json) + objectclasses = self.get_objectclasses() + + # Get the primary name of this attribute + attributetypename = attributetype.names[0] + # Build a set if they have may. + may = [oc for oc in objectclasses if attributetypename.lower() in + list(map(str.lower, oc.may))] + # Build a set if they have must. + must = [oc for oc in objectclasses if attributetypename.lower() in + list(map(str.lower, oc.must))] + + if json: + # convert Objectclass class to dict, then sort each list + may = [vars(oc) for oc in may] + must = [vars(oc) for oc in must] + # Add normalized 'name' for sorting + for oc in may: + oc['name'] = oc['names'][0].lower() + for oc in must: + oc['name'] = oc['names'][0].lower() + may = sorted(may, key=itemgetter('name')) + must = sorted(must, key=itemgetter('name')) + result = {'type': 'schema', + 'at': vars(attributetype), + 'may': may, + 'must': must} + return dump_json(result) + else: + return str(attributetype), may, must + class SchemaLegacy(object): @@ -71,12 +421,9 @@ class SchemaLegacy(object): suitable for passing to ldap.schema.SubSchema() @param filename - the full path and filename of a schema file in ldif format""" - import six.moves.urllib.request - import six.moves.urllib.parse - import ldif - ldif_file = six.moves.urllib.request.urlopen('file://' + filename) - ldif_parser = ldif.LDIFRecordList(ldif_file, max_entries=1) + with open(filename, 'r') as f: + ldif_parser = ldif.LDIFRecordList(f, max_entries=1) if not ldif_parser: return None ldif_parser.parse()