Source code for univention.admin.modules

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright 2004-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.

"""
|UDM| access to handler modules.
"""

from __future__ import absolute_import

import os
import copy
import locale
import importlib
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Text, Tuple, Union  # noqa: F401

import six
import ldap
from six.moves import reload_module
from ldap.filter import filter_format

import univention.debug as ud
import univention.admin
import univention.admin.uldap
import univention.admin.syntax
import univention.admin.handlers
import univention.admin.hook
from univention.admin import localization
from univention.admin.layout import Tab, Group, ILayoutElement
from univention.admin._ucr import configRegistry

try:
	from typing_extensions import Protocol

[docs] class UdmModule(Protocol): module = '' # type: str childs = False # type: bool operations = [] # type: List[str] short_description = '' # type: str object_name = '' # type: str object_name_plural = '' # type: str long_description = '' # type: str options = {} # type: Dict[str, univention.admin.option] property_descriptions = {} # type: Dict[str, univention.admin.property] default_property_descriptions = {} # type: Dict[str, univention.admin.property] policy_apply_to = [] # type: List[str] policy_position_dn_prefix = '' # type: str policy_oc = '' # type: str docleanup = False # type: bool layout = [] # type: List[Tab] mapping = None # type: univention.admin.mapping.mapping initialized = False # type: bool extended_attribute_tabnames = [] # type: List[str] extended_udm_attributes = [] # type: List[univention.admin.extended_attribute]
[docs] class object: def __init__(self, co, lo, position, dn=u'', superordinate=None, attributes=None): # type: (None, univention.admin.uldap.access, univention.admin.uldap.position, Text, univention.admin.handlers.simpleLdap, univention.admin.handlers._Attributes) -> None pass
[docs] @staticmethod def identify(dn, attr): # type: (str, Dict[str, List[Any]]) -> bool pass
[docs] @staticmethod def lookup(co, lo, filter='', base='', superordinate=None, scope='base+one', unique=False, required=False, timeout=-1, sizelimit=0): # type: (None, univention.admin.uldap.access, str, str, Any, str, bool, bool, int, int) -> List[Any] pass
UdmName = Union[UdmModule, str] except ImportError: pass translation = localization.translation('univention/admin') _ = translation.translate modules = {} # type: Dict[str, UdmModule] """Mapping from module name to Python module.""" _superordinates = set() # type: Set[str] """List of all module names (strings) that are _superordinates.""" containers = [] # type: List[UdmModule]
[docs]def update(): # type: () -> None """ Scan file system and update internal list of |UDM| handler modules. """ global modules, _superordinates _modules = {} # type: Dict[str, UdmModule] superordinates = set() # type: Set[str] # since last update(), syntax.d and hooks.d may have changed (Bug #31154) univention.admin.syntax.import_syntax_files() univention.admin.hook.import_hook_files() def _walk(root, dir, files): # type: (str, str, List[str]) -> None for file in files: if not file.endswith('.py') or file.startswith('__'): continue package = os.path.join(dir, file)[len(root) + 1:-len('.py')] ud.debug(ud.ADMIN, ud.INFO, 'admin.modules.update: importing "%s"' % (package,)) modulepackage = '.'.join(package.split(os.path.sep)) m = importlib.import_module('univention.admin.handlers.%s' % (modulepackage,)) # type: Any m.initialized = False if not hasattr(m, 'module'): ud.debug(ud.ADMIN, ud.ERROR, 'admin.modules.update: attribute "module" is missing in module %r' % (modulepackage,)) continue _modules[m.module] = m if isContainer(m): containers.append(m) superordinates.update(superordinate_names(m)) for root in univention.admin.handlers.__path__: # type: ignore for w_root, w_dirs, w_files in os.walk(root): _walk(root, w_root, w_files) modules = _modules _superordinates = superordinates # since last update(), syntax.d may have new choices # put here as one syntax wants to provide all modules univention.admin.syntax.update_choices()
[docs]def get(module): # type: (UdmName) -> UdmModule """ Get |UDM| module. :param module: either the name (str) of a module or the module itself. :returns: the module or `None` if no module exists with the requested name. """ # FIXME: raise Exception instead of returning None if not module: return None # type: ignore if isinstance(module, six.string_types): return modules.get(module) # type: ignore return module
[docs]def get_module(module): # type: (UdmName) -> Optional[UdmModule] """ interim function, must only be used by `univention-directory-manager-modules`! .. deprecated :: UCS 4.4 :param module: either the name (str) of a module or the module itself. :returns: the module or `None` if no module exists with the requested name. """ if not modules: ud.debug(ud.ADMIN, ud.WARN, 'univention.admin.modules.update() was not called') update() return get(module)
[docs]def init(lo, position, module, template_object=None, force_reload=False): # type: (univention.admin.uldap.access, univention.admin.uldap.position, UdmModule, univention.admin.handlers.simpleLdap, bool) -> None """ Initialize |UDM| handler module. :param lo: |LDAP| connection. :param position: |UDM| position instance. :param module: |UDM| handler module. :param template_object: Reference to a instance, from which the default values are used. :param force_reload: With `True` force Python to reload the module from the file system. """ # you better do a reload if init is called a second time # especially because update_extended_attributes # called twice will have side-effects if force_reload: reload_module(module) # type: ignore # reset property descriptions to defaults if possible if hasattr(module, 'default_property_descriptions'): module.property_descriptions = copy.deepcopy(module.default_property_descriptions) # ud.debug(ud.ADMIN, ud.INFO, 'modules_init: reset default descriptions') # overwrite property descriptions univention.admin.ucr_overwrite_properties(module, lo) # check for properties with the syntax class LDAP_Search for pname, prop in module.property_descriptions.items(): if prop.syntax.name == 'LDAP_Search': prop.syntax._load(lo) if prop.syntax.viewonly: module.mapping.unregister(pname, False) elif univention.admin.syntax.is_syntax(prop.syntax, univention.admin.syntax.complex) and hasattr(prop.syntax, 'subsyntaxes'): for text, subsyn in prop.syntax.subsyntaxes: if subsyn.name == 'LDAP_Search': subsyn._load(lo) # add new properties update_extended_options(lo, module, position) update_extended_attributes(lo, module, position) # get defaults from template if template_object: ud.debug(ud.ADMIN, ud.INFO, 'modules_init: got template object %s' % template_object.dn) template_object.open() # add template ext. attr. defaults if hasattr(template_object, 'property_descriptions'): for property_name, property in template_object.property_descriptions.items(): if not (property_name == "name" or property_name == "description"): default = property.base_default if default and property_name in module.property_descriptions: if property.multivalue: if module.property_descriptions[property_name].multivalue: module.property_descriptions[property_name].base_default = list(default) else: module.property_descriptions[property_name].base_default = default ud.debug(ud.ADMIN, ud.INFO, "modules.init: added template default (%s) to property %s" % (property.base_default, property_name)) # add template defaults for key in template_object.keys(): if not (key == "name" or key == "description"): # these keys are part of the template itself if key == '_options': if template_object[key] != [''] and template_object[key] != []: for option in module.options.keys(): module.options[option].default = option in template_object[key] else: if template_object.descriptions[key].multivalue: if module.property_descriptions[key].multivalue: module.property_descriptions[key].base_default = list(template_object[key]) else: ud.debug(ud.ADMIN, ud.INFO, 'modules.init: template and object values not both multivalue !!') else: module.property_descriptions[key].base_default = template_object[key] module.property_descriptions[key].templates.append(template_object) ud.debug(ud.ADMIN, ud.INFO, 'modules_init: module.property_description after template: %s' % module.property_descriptions) else: ud.debug(ud.ADMIN, ud.INFO, 'modules_init: got no template') # re-build layout if there any overwrites defined univention.admin.ucr_overwrite_module_layout(module) # some choices depend on extended_options/attributes univention.admin.syntax.update_choices() module.initialized = True
[docs]def update_extended_options(lo, module, position): # type: (univention.admin.uldap.access, UdmModule, univention.admin.uldap.position) -> None """ Overwrite options defined via |LDAP|. """ # get current language lang = locale.getlocale(locale.LC_MESSAGES)[0] ud.debug(ud.ADMIN, ud.INFO, 'modules update_extended_options: LANG=%s' % lang) module_filter = filter_format('(univentionUDMOptionModule=%s)', [name(module)]) if name(module) == 'settings/usertemplate': module_filter = '(|(univentionUDMOptionModule=users/user)%s)' % (module_filter,) # append UDM extended options new_options = copy.copy(module.options) if hasattr(module, 'options') else {} for dn, attrs in lo.search(base=position.getDomainConfigBase(), filter='(&(objectClass=univentionUDMOption)%s)' % (module_filter,)): oname = attrs['cn'][0].decode('UTF-8', 'replace') shortdesc = _get_translation(lang, attrs, 'univentionUDMOptionTranslationShortDescription;entry-%s', 'univentionUDMOptionShortDescription') longdesc = _get_translation(lang, attrs, 'univentionUDMOptionTranslationLongDescription;entry-%s', 'univentionUDMOptionLongDescription') default = attrs.get('univentionUDMOptionDefault', [b'0'])[0] == b'1' editable = attrs.get('univentionUDMOptionEditable', [b'0'])[0] == b'1' classes = [x.decode('UTF-8', 'replace') for x in attrs.get('univentionUDMOptionObjectClass', [])] is_app_option = attrs.get('univentionUDMOptionIsApp', [b'0'])[0] == b'1' new_options[oname] = univention.admin.option( short_description=shortdesc, long_description=longdesc, default=default, editable=editable, objectClasses=classes, is_app_option=is_app_option) module.options = new_options
[docs]class EA_Layout(dict): """ Extended attribute layout. """ def __init__(self, **kwargs): dict.__init__(self, kwargs) @property def name(self): # type: () -> str return self.get('name', '') @property def overwrite(self): # type: () -> Optional[str] return self.get('overwrite', None) @property def tabName(self): # type: () -> str return self.get('tabName', '') @property def groupName(self): # type: () -> str return self.get('groupName', '') @property def position(self): # type: () -> int return self.get('position', -1) @property def groupPosition(self): # type: () -> int return self.get('groupPosition', -1) @property def advanced(self): # type: () -> bool return self.get('advanced', False) @property def is_app_tab(self): # type: () -> bool return self.get('is_app_tab', False) def __lt__(self, other): return (self.groupName, self.position) < (other.groupName, other.position) def __gt__(self, other): return (self.groupName, self.position) > (other.groupName, other.position) def __eq__(self, other): return (self.groupName, self.position) == (other.groupName, other.position) def __le__(self, other): return (self.groupName, self.position) <= (other.groupName, other.position) def __ge__(self, other): return (self.groupName, self.position) >= (other.groupName, other.position) def __ne__(self, other): return (self.groupName, self.position) != (other.groupName, other.position) def __hash__(self): return hash((self.groupName, self.position))
[docs]def update_extended_attributes(lo, module, position): # type: (univention.admin.uldap.access, UdmModule, univention.admin.uldap.position) -> None """ Load extended attribute from |LDAP| and modify |UDM| handler. """ # add list of tabnames created by extended attributes if not hasattr(module, 'extended_attribute_tabnames'): module.extended_attribute_tabnames = [] # append UDM extended attributes properties4tabs = {} # type: Dict[str, List[EA_Layout]] overwriteTabList = [] # type: List[str] module.extended_udm_attributes = [] module_filter = filter_format('(univentionUDMPropertyModule=%s)', [name(module)]) if name(module) == 'settings/usertemplate': module_filter = '(|(univentionUDMPropertyModule=users/user)%s)' % (module_filter,) new_property_descriptions = copy.copy(module.property_descriptions) for dn, attrs in lo.search(base=position.getDomainConfigBase(), filter='(&(objectClass=univentionUDMProperty)%s(univentionUDMPropertyVersion=2))' % (module_filter,)): # get CLI name pname = attrs['univentionUDMPropertyCLIName'][0].decode('UTF-8', 'replace') object_class = attrs.get('univentionUDMPropertyObjectClass', [])[0].decode('UTF-8', 'replace') if name(module) == 'settings/usertemplate' and object_class == 'univentionMail' and b'settings/usertemplate' not in attrs.get('univentionUDMPropertyModule', []): continue # since "mail" is a default option, creating a usertemplate with any mail attribute would raise Object class violation: object class 'univentionMail' requires attribute 'uid' # get syntax propertySyntaxString = attrs.get('univentionUDMPropertySyntax', [b''])[0].decode('utf-8', 'replace') if propertySyntaxString and hasattr(univention.admin.syntax, propertySyntaxString): propertySyntax = getattr(univention.admin.syntax, propertySyntaxString) else: if lo.searchDn(filter=filter_format(univention.admin.syntax.LDAP_Search.FILTER_PATTERN, [propertySyntaxString])): propertySyntax = univention.admin.syntax.LDAP_Search(propertySyntaxString) else: propertySyntax = univention.admin.syntax.string() # get hooks propertyHookString = attrs.get('univentionUDMPropertyHook', [b''])[0].decode('utf-8', 'replace') propertyHook = None if propertyHookString and hasattr(univention.admin.hook, propertyHookString): propertyHook = getattr(univention.admin.hook, propertyHookString)() # get default value propertyDefault = [x.decode('UTF-8') if x is not None else x for x in attrs.get('univentionUDMPropertyDefault', [None])] # value may change try: mayChange = int(attrs.get('univentionUDMPropertyValueMayChange', [b'0'])[0]) except ValueError: ud.debug(ud.ADMIN, ud.ERROR, 'modules update_extended_attributes: ERROR: processing univentionUDMPropertyValueMayChange threw exception - assuming mayChange=0') mayChange = 0 # value is editable (only via hooks or direkt module.info[] access) editable = attrs.get('univentionUDMPropertyValueNotEditable', [b'0'])[0] not in [b'1', b'TRUE'] copyable = attrs.get('univentionUDMPropertyCopyable', [b'0'])[0] not in [b'1', b'TRUE'] # value is required valueRequired = (attrs.get('univentionUDMPropertyValueRequired', [b'0'])[0].upper() in [b'1', b'TRUE']) # value not available for searching try: doNotSearch = int(attrs.get('univentionUDMPropertyDoNotSearch', [b'0'])[0]) except ValueError: ud.debug(ud.ADMIN, ud.ERROR, 'modules update_extended_attributes: ERROR: processing univentionUDMPropertyDoNotSearch threw exception - assuming doNotSearch=0') doNotSearch = 0 # check if CA is multivalue property if attrs.get('univentionUDMPropertyMultivalue', [b''])[0] == b'1': multivalue = True map_method = None unmap_method = None else: multivalue = False map_method = univention.admin.mapping.ListToString unmap_method = None if propertySyntaxString == 'boolean': map_method = univention.admin.mapping.BooleanListToString unmap_method = univention.admin.mapping.BooleanUnMap # single value ==> use only first value propertyDefault = propertyDefault[0] # Show this attribute in UDM/UMC? if attrs.get('univentionUDMPropertyLayoutDisable', [b''])[0] == b'1': layoutDisabled = True else: layoutDisabled = False # get current language lang = locale.getlocale(locale.LC_MESSAGES)[0] ud.debug(ud.ADMIN, ud.INFO, 'modules update_extended_attributes: LANG = %s' % str(lang)) # get descriptions shortdesc = _get_translation(lang, attrs, 'univentionUDMPropertyTranslationShortDescription;entry-%s', 'univentionUDMPropertyShortDescription') longdesc = _get_translation(lang, attrs, 'univentionUDMPropertyTranslationLongDescription;entry-%s', 'univentionUDMPropertyLongDescription') # create property fullWidth = (attrs.get('univentionUDMPropertyLayoutFullWidth', [b'0'])[0].upper() in [b'1', b'TRUE']) new_property_descriptions[pname] = univention.admin.property( short_description=shortdesc, long_description=longdesc, syntax=propertySyntax, multivalue=multivalue, options=[x.decode('UTF-8', 'replace') for x in attrs.get('univentionUDMPropertyOptions', [])], required=valueRequired, may_change=mayChange, dontsearch=doNotSearch, default=propertyDefault, editable=editable, copyable=copyable, size='Two' if fullWidth else None, ) # add LDAP mapping if attrs['univentionUDMPropertyLdapMapping'][0].lower() != b'objectClass'.lower(): module.mapping.register(pname, attrs['univentionUDMPropertyLdapMapping'][0].decode('UTF-8', 'replace'), unmap_method, map_method) else: module.mapping.register(pname, attrs['univentionUDMPropertyLdapMapping'][0].decode('UTF-8', 'replace'), univention.admin.mapping.nothing, univention.admin.mapping.nothing) if hasattr(module, 'layout'): tabname = _get_translation(lang, attrs, 'univentionUDMPropertyTranslationTabName;entry-%s', 'univentionUDMPropertyLayoutTabName', _('Custom')) overwriteTab = (attrs.get('univentionUDMPropertyLayoutOverwriteTab', [b'0'])[0].upper() in [b'1', b'TRUE']) # in the first generation of extended attributes of version 2 # this field was a position defining the attribute to # overwrite. now it is the name of the attribute to overwrite overwriteProp = attrs.get('univentionUDMPropertyLayoutOverwritePosition', [b''])[0].decode('UTF-8', 'replace') if overwriteProp == '0': overwriteProp = None deleteObjectClass = (attrs.get('univentionUDMPropertyDeleteObjectClass', [b'0'])[0].upper() in [b'1', b'TRUE']) tabAdvanced = (attrs.get('univentionUDMPropertyLayoutTabAdvanced', [b'0'])[0].upper() in [b'1', b'TRUE']) groupname = _get_translation(lang, attrs, 'univentionUDMPropertyTranslationGroupName;entry-%s', 'univentionUDMPropertyLayoutGroupName') try: groupPosition = int(attrs.get('univentionUDMPropertyLayoutGroupPosition', [b'-1'])[0]) except TypeError: groupPosition = 0 ud.debug(ud.ADMIN, ud.INFO, 'update_extended_attributes: extended attribute (LDAP): %r' % (attrs,)) # only one is possible ==> overwriteTab wins if overwriteTab and overwriteProp: overwriteProp = None # add tab name to list if missing if tabname not in properties4tabs and not layoutDisabled: properties4tabs[tabname] = [] ud.debug(ud.ADMIN, ud.INFO, 'modules update_extended_attributes: custom fields init for tab %s' % tabname) # remember tab for purging if required if overwriteTab and tabname not in overwriteTabList and not layoutDisabled: overwriteTabList.append(tabname) if not layoutDisabled: # get position on tab # -1 == append on top priority = attrs.get('univentionUDMPropertyLayoutPosition', [b'-1'])[0].decode('UTF-8', 'replace') try: priority = int(priority) if priority < 1: priority = -1 except ValueError: ud.debug(ud.ADMIN, ud.WARN, 'modules update_extended_attributes: custom field for tab %s: failed to convert tabNumber to int' % tabname) priority = -1 if priority == -1 and properties4tabs[tabname]: priority = max([-1, min((ea_layout.position for ea_layout in properties4tabs[tabname])) - 1]) properties4tabs[tabname].append(EA_Layout( name=pname, tabName=tabname, position=priority, advanced=tabAdvanced, overwrite=overwriteProp, fullWidth=fullWidth, groupName=groupname, groupPosition=groupPosition, is_app_tab=any(option in [key for (key, value) in getattr(module, 'options', {}).items() if value.is_app_option] for option in attrs.get('univentionUDMPropertyOptions', [])), )) else: for tab in getattr(module, 'layout', []): tab.remove(pname) module.extended_udm_attributes.append(univention.admin.extended_attribute( name=pname, objClass=object_class, ldapMapping=attrs['univentionUDMPropertyLdapMapping'][0].decode('UTF-8', 'replace'), deleteObjClass=deleteObjectClass, syntax=propertySyntaxString, hook=propertyHook )) module.property_descriptions = new_property_descriptions # overwrite tabs that have been added by UDM extended attributes for tab in module.extended_attribute_tabnames: if tab not in overwriteTabList: overwriteTabList.append(tab) if properties4tabs: # remove layout of tabs that have been marked for replacement for tab in module.layout: if tab.label in overwriteTabList: tab.layout = [] for tabname, priofields in properties4tabs.items(): priofields = sorted(priofields) currentTab = None # get existing fields if tab has not been overwritten for tab in module.layout: if tab.label == tabname: # found tab in layout currentTab = tab # tab found ==> leave loop break else: # tab not found in current layout, so add it currentTab = Tab(tabname, tabname, advanced=True) module.layout.append(currentTab) # remember tabs that have been added by UDM extended attributes if tabname not in module.extended_attribute_tabnames: module.extended_attribute_tabnames.append(tabname) currentTab.is_app_tab = any(x.is_app_tab for x in priofields) # check if tab is empty ==> overwritePosition is impossible freshTab = len(currentTab.layout) == 0 for ea_layout in priofields: if currentTab.advanced and not ea_layout.advanced: currentTab.advanced = False # if groupName is set check if it exists, otherwise create it if ea_layout.groupName: for item in currentTab.layout: if isinstance(item, ILayoutElement) and item.label == ea_layout.groupName: break else: # group does not exist grp = Group(ea_layout.groupName) if ea_layout.groupPosition > 0: currentTab.layout.insert(ea_layout.groupPosition - 1, grp) else: currentTab.layout.append(grp) # - existing property shall be overwritten AND # - tab is not new and has not been cleaned before AND # - position >= 1 (top left position is defined as 1) AND # - old property with given position exists if currentTab.exists(ea_layout.name): continue elif ea_layout.overwrite and not freshTab: # we want to overwrite an existing property # in the global fields ... if not ea_layout.groupName: replaced, layout = currentTab.replace(ea_layout.overwrite, ea_layout.name, recursive=True) if not replaced: # the property was not found so we'll append it currentTab.layout.append(ea_layout.name) else: for item in currentTab.layout: if isinstance(item, ILayoutElement) and item.label == ea_layout.groupName: replaced, layout = item.replace(ea_layout.overwrite, ea_layout.name) if not replaced: # the property was not found so we'll append it item.layout.append(ea_layout.name) else: if not ea_layout.groupName: currentTab.insert(ea_layout.position, ea_layout.name) else: for item in currentTab.layout: if isinstance(item, ILayoutElement) and item.label == ea_layout.groupName: item.insert(ea_layout.position, ea_layout.name) break # check for properties with the syntax class LDAP_Search for pname, prop in module.property_descriptions.items(): if prop.syntax.name == 'LDAP_Search': prop.syntax._load(lo) if prop.syntax.viewonly: module.mapping.unregister(pname, False) elif univention.admin.syntax.is_syntax(prop.syntax, univention.admin.syntax.complex) and hasattr(prop.syntax, 'subsyntaxes'): for text, subsyn in prop.syntax.subsyntaxes: if subsyn.name == 'LDAP_Search': subsyn._load(lo)
[docs]def identify(dn, attr, module_name='', canonical=0, module_base=None): # type: (str, Dict[str, List[Any]], str, int, Optional[str]) -> List[UdmModule] """ Return list of |UDM| handlers capable of handling the given |LDAP| object. :param dn: |DN| of the |LDAP| object. :param attr: |LDAP| attributes. :param module_name: If given only the given module name is used if it is capable to handle the object. :param canonical: UNUSED! :param module_base: Optional string the module names must start with. :returns: the list of |UDM| modules. """ res = [m for m in ( modules.get(mt.decode('ASCII', 'replace')) for mt in attr.get('univentionObjectType', []) ) if m] if not res: for name, module in modules.items(): if module_base is not None and not name.startswith(module_base): continue if not hasattr(module, 'identify'): ud.debug(ud.ADMIN, ud.INFO, 'module %s does not provide identify' % module) continue if (not module_name or module_name == module.module) and module.identify(dn, attr): res.append(module) if not res: ud.debug(ud.ADMIN, ud.INFO, 'object could not be identified') for r in res: ud.debug(ud.ADMIN, ud.INFO, 'identify: found module %s on %s' % (r.module, dn)) return res
[docs]def identifyOne(dn, attr, type=''): # type: (str, Dict[str, List[Any]], str) -> Optional[UdmModule] """ Return the |UDM| handler capable of handling the given |LDAP| object. :param dn: |DN| of the |LDAP| object. :param atr: |LDAP| attributes. :param type: If given only the given module name is used if it is capable to handle the object. :returns: the |UDM| modules or `None`. """ res = identify(dn, attr, type) if len(res) != 1: return None else: return res[0]
[docs]def recognize(module_name, dn, attr): # type: (str, str, Dict[str, List[Any]]) -> bool module = get(module_name) if not hasattr(module, 'identify'): return False return module.identify(dn, attr)
[docs]def name(module): # type: (UdmName) -> str """ Return name of module. """ if not module: return '' return get(module).module
[docs]def superordinate_names(module_name): # type: (UdmName) -> List[str] """ Return name of superordinate module. """ module = get(module_name) names = getattr(module, 'superordinate', []) if isinstance(names, six.string_types): names = [names] return names
[docs]def superordinate_name(module_name): """ Return name of first superordinate module. .. deprecated :: UCS 4.2 Use :py:func:`superordinate_names` instead. """ names = superordinate_names(module_name) return names[0] if names else None
[docs]def superordinate(module): """ Return instance of superordinate module. .. deprecated :: UCS 4.2 Use :py:func:`superordinates` instead. """ return get(superordinate_name(module))
[docs]def superordinates(module): # type: (UdmName) -> List[Optional[UdmModule]] """ Return instance of superordinate module. """ return [get(x) for x in superordinate_names(module)]
[docs]def subordinates(module): # type: (UdmName) -> List[UdmModule] """ Return list of instances of subordinate modules. :param module: ??? :returns: list of |UDM| handler modules. """ return [mod for mod in modules.values() if name(module) in superordinate_names(mod) and not isContainer(mod)]
[docs]def find_superordinate(dn, co, lo): # type: (str, None, univention.admin.uldap.access) -> Optional[UdmModule] """ For a given |DN|, search in the |LDAP| path whether this LDAP object is below an object that is a superordinate or is a superordinate itself. :param dn: |DN|. :param co: |UDM| configuation object. :param lo: |LDAP| connection. :returns: the superordinate module or `None`. """ # walk up the ldap path and stop if we find an object type that is a superordinate while dn: attr = lo.get(dn) module = identifyOne(dn, attr) if module and isSuperordinate(module): return get(module) dn = lo.parentDn(dn) return None
[docs]def layout(module_name, object=None): # type: (UdmName, Any) -> List[Tab] """return layout of properties""" module = get(module_name) defining_layout = None if object: ud.debug(ud.ADMIN, ud.ALL, 'modules.py layout: got a defined object') if object and hasattr(object, 'layout'): # for dynamic modules like users/self ud.debug(ud.ADMIN, ud.ALL, 'modules.py layout:: layout is defined by the object') defining_layout = object.layout elif hasattr(module, 'layout'): defining_layout = module.layout ud.debug(ud.ADMIN, ud.ALL, 'modules.py layout:: layout is defined by the module') if defining_layout: if object and hasattr(object, 'options'): layout = [] for tab in defining_layout: empty = True fields = [] for line in tab.layout: nline = [] for row in line: single = False nrow = [] if isinstance(row, six.string_types): single = True row = [row] for field in row: prop = module.property_descriptions[field] nrow.append(field) if not prop.options or [opt for opt in prop.options if opt in object.options]: if not prop.license or [license for license in prop.license if license in object.lo.licensetypes]: empty = False if nrow: if single: nrow = nrow[0] nline.append(nrow) if nline: fields.append(nline) if fields and not empty: ntab = copy.deepcopy(tab) ntab.layout = fields layout.append(ntab) ud.debug(ud.ADMIN, ud.ALL, 'modules.py layout:: return layout decreased by given options') return layout else: ud.debug(ud.ADMIN, ud.ALL, 'modules.py layout:: return defining_layout.') return defining_layout else: return []
[docs]def options(module_name): # type: (UdmName) -> Dict[str, Any] """return options available for module""" module = get(module_name) return getattr(module, 'options', {})
[docs]def attributes(module_name): # type: (UdmName) -> List[Dict[str, str]] """ Return attributes for module. :param module_name: the name of the |UDM| module, e.g. `users/user`. """ module = get(module_name) return [ {'name': attribute, 'description': module.property_descriptions[attribute].short_description} for attribute in module.property_descriptions.keys() ]
[docs]def short_description(module_name): # type: (UdmName) -> str """ Return short description for module. :param module_name: the name of the |UDM| module, e.g. `users/user`. :returns: The short descriptive text. """ module = get(module_name) if hasattr(module, 'short_description'): return module.short_description modname = name(module) if modname: return modname return repr(module)
[docs]def policy_short_description(module_name): # type: (UdmName) -> str """ Return short description for policy module primarily used for tab headers. :param module_name: the name of the |UDM| policy module, e.g. `policies/pwhistory`. :returns: The short descriptive text. """ module = get(module_name) return getattr(module, 'policy_short_description', short_description(module))
[docs]def long_description(module_name): # type: (UdmName) -> str """ Return long description for module. :param module_name: the name of the |UDM| module, e.g. `users/user`. :returns: The long descriptive text. """ module = get(module_name) return getattr(module, 'long_description', short_description(module))
[docs]def childs(module_name): # type: (UdmName) -> bool """ Return whether module may have subordinate modules. :param module_name: the name of the |UDM| module, e.g. `users/user`. :returns: `True` if the module has children, `False` otherwise. """ module = get(module_name) return getattr(module, 'childs', False)
[docs]def virtual(module_name): # type: (UdmName) -> bool """ Return whether the module is virtual (alias for other modules). :param module_name: the name of the |UDM| module, e.g. `computers/computer`. :returns: `True` if the module is virtual, `False` otherwise. """ module = get(module_name) return getattr(module, 'virtual', False)
[docs]def lookup(module_name, co, lo, filter='', base='', superordinate=None, scope='base+one', unique=False, required=False, timeout=-1, sizelimit=0): # type: (UdmName, None, univention.admin.uldap.access, str, str, Any, str, bool, bool, int, int) -> List[Any] """ Return objects of module that match the given criteria. :param module_name: the name of the |UDM| module, e.g. `users/user`. """ module = get(module_name) tmpres = [] if hasattr(module, 'lookup'): tmpres = module.lookup(co, lo, filter, base=base, superordinate=superordinate, scope=scope, unique=unique, required=required, timeout=timeout, sizelimit=sizelimit) # check for 'None' items just in case... return [item for item in tmpres if item]
[docs]def isSuperordinate(module): # type: (UdmName) -> bool """ Check if the module is a |UDM| superordinate module. :param module: A |UDM| handler class. :returns: `True` if the handler is a superordinate module, `False` otherwise. """ return name(module) in _superordinates
[docs]def isContainer(module): # type: (UdmModule) -> bool """ Check if the module is a |UDM| container module. :param module: A |UDM| handler class. :returns: `True` if the handler is a container module, `False` otherwise. """ return name(module).startswith('container/')
[docs]def isPolicy(module): # type: (UdmModule) -> bool """ Check if the module is a |UDM| policy module. :param module: A |UDM| handler class. :returns: `True` if the handler is a policy module, `False` otherwise. """ return name(module).startswith('policies/')
[docs]def defaultPosition(module, superordinate=None): # type: (UdmModule, Any) -> str """ Returns default position for object of module. :param module: A |UDM| handler class. :param superordinate: A optional superordinate |UDM| object instance. :returns: The |DN| of the container for the object. """ rdns = ['users', 'dns', 'dhcp', 'shares', 'printers'] base = univention.admin.uldap.getBaseDN() if superordinate: return superordinate.dn start = name(module).split('/')[0] if start in rdns: return 'cn=%s,%s' % (ldap.dn.escape_dn_chars(start), base) return base
[docs]def supports(module_name, operation): # type: (str, str) -> bool """ Check if module supports operation :param module_name: the name of the |UDM| module, e.g. `users/user`. :param operation: the name of the operation, e.g. 'edit'. :returns: `True` if the operation is supported, `False` otherwise. """ module = get(module_name) if not hasattr(module, 'operations'): return True return operation in module.operations
[docs]def objectType(co, lo, dn, attr=None, modules=[], module_base=None): # type: (None, univention.admin.uldap.access, str, Optional[Dict[str, List[bytes]]], List[UdmModule], Optional[str]) -> List[str] if not dn: return [] if attr is None: attr = lo.get(dn) if not attr: return [] ot = attr.get('univentionObjectType') if ot: return [x.decode('utf-8') for x in ot] if not modules: modules = identify(dn, attr, module_base=module_base) return [name(mod) for mod in modules]
[docs]def objectShadowType(co, lo, dn, attr=None, modules=[]): # type: (None, univention.admin.uldap.access, str, Optional[Dict[str, List[bytes]]], List[UdmModule]) -> List[Any] # FIXME: This returns a nested List[...List[str]] for containers! return [ objectShadowType(co, lo, lo.parentDn(dn)) if otype and otype.startswith('container/') else otype for otype in objectType(co, lo, dn, attr, modules) ]
[docs]def findObject(co, lo, dn, type, attr=None, module_base=None): # type: (None, univention.admin.uldap.access, str, str, Optional[Dict[str, List[bytes]]], Optional[str]) -> Optional[Any] if attr is None: attr = lo.get(dn) if not attr: return None ndn = dn nattr = attr while True: for module in identify(ndn, nattr): if module and module.module == type: s = superordinate(module) if s: so = findObject(co, lo, ndn, s) else: so = None return module.object(co, lo, ndn, superordinate=so) ndn = lo.parentDn(ndn) if not ndn: break nattr = lo.get(ndn) return None
[docs]def policyOc(module_name): # type: (UdmName) -> str """ Return the |LDAP| objectClass used to store the policy. :param module_name: the name of the |UDM| policy module, e.g. `policies/pwhistory`. :returns: the objectClass. """ module = get(module_name) return getattr(module, 'policy_oc', '')
[docs]def policiesGroup(module_name): # type: (UdmName) -> str """ Return the name of the group the |UDM| policy belongs to. :param module_name: the name of the |UDM| policy module, e.g. `policies/pwhistory`. :returns: the group name. """ module = get(module_name) return getattr(module, 'policies_group', 'top')
[docs]def policies(): # type: () -> List[univention.admin.policiesGroup] res = {} # type: Dict[str, List[str]] for mod in modules.values(): if not isPolicy(mod): continue if not name(mod) == 'policies/policy': res.setdefault(policiesGroup(mod), []).append(name(mod)) return [ univention.admin.policiesGroup(id=groupname, members=sorted(members)) for groupname, members in sorted(res.items()) ]
[docs]def policyTypes(module_name): # type: (str) -> List[str] """ Returns a list of policy types applying to the given module. :param module_name: the name of the |UDM| module, e.g. `users/user`. :returns: a list of |UDM| policy modules, e.g. `policies/pwhistory`. """ if not module_name: return [] if module_name not in modules: return [] return [ name for name, module in modules.items() if name.startswith('policies/') and module_name in getattr(module, 'policy_apply_to', ()) ]
[docs]def policyPositionDnPrefix(module_name): # type: (UdmName) -> str """ Return the relative |DN| for a policy. :param module_name: the name of the |UDM| policy module, e.g. `policies/pwhistory`. :return: A |DN| string to append to the |LDAP| base to get the container for the policy. """ module = get(module_name) if not hasattr(module, 'policy_position_dn_prefix'): return "" policy_position_dn_prefix = module.policy_position_dn_prefix if policy_position_dn_prefix.endswith(','): policy_position_dn_prefix = policy_position_dn_prefix[:-1] return policy_position_dn_prefix
[docs]def defaultContainers(module): # type: (univention.admin.handlers.simpleLdap) -> List[str] """ Checks for the attribute default_containers that should contain a list of RDNs of default containers. :param module: |UDM| :returns: a list of DNs. """ base = configRegistry['ldap/base'] return ['%s,%s' % (rdn, base) for rdn in getattr(module, 'default_containers', [])]
[docs]def childModules(module_name): # type: (UdmName) -> List[str] """ Return child modules if module is a super module. :param module_name: the name of the |UDM| module, e.g. `users/user`. :returns: List of child module names. """ module = get(module_name) return list(getattr(module, 'childmodules', []))
def _get_translation(locale, attrs, name, defaultname, default=u''): # type: (str, Any, str, str, str) -> str if locale: locale = locale.replace(u'_', u'-').lower() if name % (locale,) in attrs: return attrs[name % (locale,)][0].decode('UTF-8', 'replace') locale = locale.split(u'-', 1)[0] name_short_lang = name % (locale,) if name_short_lang in attrs: return attrs[name_short_lang][0].decode('UTF-8', 'replace') for key in attrs: if key.startswith(name_short_lang): return attrs[key][0].decode('UTF-8', 'replace') return attrs.get(defaultname, [default.encode('utf-8')])[0].decode('UTF-8', 'replace')