# -*- coding: utf-8 -*-
#
# Copyright 2018-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| module for the user contact objects
"""
from __future__ import absolute_import
import univention.admin
from univention.admin.layout import Tab, Group
import univention.admin.filter
import univention.admin.handlers
import univention.admin.allocators
import univention.admin.localization
import univention.admin.uexceptions
import univention.debug as ud
from univention.admin.handlers.users.user import mapHomePostalAddress, unmapHomePostalAddress
translation = univention.admin.localization.translation('univention.admin.handlers.users')
_ = translation.translate
module = 'users/contact'
operations = ['add', 'edit', 'remove', 'search', 'move']
childs = False
short_description = _('Contact')
object_name = _('Contact')
object_name_plural = _('Contact information')
long_description = _('Contact information')
options = {
'default': univention.admin.option(
short_description=short_description,
default=True,
objectClasses=['top', 'person', 'inetOrgPerson', 'organizationalPerson']
)
}
property_descriptions = {
'cn': univention.admin.property(
short_description=_('Name'),
long_description='',
syntax=univention.admin.syntax.TwoThirdsString,
include_in_default_search=True,
identifies=True,
readonly_when_synced=True,
copyable=True,
),
'lastname': univention.admin.property(
short_description=_('Last name'),
long_description='',
syntax=univention.admin.syntax.string,
include_in_default_search=True,
required=True,
readonly_when_synced=True,
copyable=True,
),
'firstname': univention.admin.property(
short_description=_('First name'),
long_description='',
syntax=univention.admin.syntax.TwoThirdsString,
include_in_default_search=True,
readonly_when_synced=True,
copyable=True,
),
'title': univention.admin.property(
short_description=_('Title'),
long_description='',
syntax=univention.admin.syntax.OneThirdString,
readonly_when_synced=True,
copyable=True,
),
'initials': univention.admin.property(
short_description=_('Initials'),
long_description='',
syntax=univention.admin.syntax.string6,
readonly_when_synced=True,
copyable=True,
),
'description': univention.admin.property(
short_description=_('Description'),
long_description='',
syntax=univention.admin.syntax.string,
include_in_default_search=True,
readonly_when_synced=True,
copyable=True,
),
'displayName': univention.admin.property(
short_description=_('Display name'),
long_description='',
syntax=univention.admin.syntax.string,
default='<firstname> <lastname><:strip>',
readonly_when_synced=True,
copyable=True,
),
'birthday': univention.admin.property(
short_description=_('Birthdate'),
long_description='',
syntax=univention.admin.syntax.iso8601Date,
copyable=True,
),
'jpegPhoto': univention.admin.property(
short_description=_("Picture of the user (JPEG format)"),
long_description=_('Picture for user account in JPEG format'),
syntax=univention.admin.syntax.jpegPhoto,
dontsearch=True,
copyable=True,
),
'organisation': univention.admin.property(
short_description=_('Organisation'),
long_description='',
syntax=univention.admin.syntax.string64,
readonly_when_synced=True,
copyable=True,
),
'employeeNumber': univention.admin.property(
short_description=_('Employee number'),
long_description='',
syntax=univention.admin.syntax.string,
include_in_default_search=True,
copyable=True,
),
'employeeType': univention.admin.property(
short_description=_('Employee type'),
long_description='',
syntax=univention.admin.syntax.string,
copyable=True,
),
'secretary': univention.admin.property(
short_description=_('Superior'),
long_description='',
syntax=univention.admin.syntax.UserDN,
multivalue=True,
copyable=True,
),
'e-mail': univention.admin.property(
short_description=_('E-mail address'),
long_description='',
syntax=univention.admin.syntax.emailAddress,
multivalue=True,
),
'phone': univention.admin.property(
short_description=_('Telephone number'),
long_description='',
syntax=univention.admin.syntax.phone,
multivalue=True,
readonly_when_synced=True,
copyable=True,
),
'roomNumber': univention.admin.property(
short_description=_('Room number'),
long_description='',
syntax=univention.admin.syntax.OneThirdString,
multivalue=True,
copyable=True,
),
'departmentNumber': univention.admin.property(
short_description=_('Department number'),
long_description='',
syntax=univention.admin.syntax.OneThirdString,
multivalue=True,
copyable=True,
),
'street': univention.admin.property(
short_description=_('Street'),
long_description='',
syntax=univention.admin.syntax.string,
readonly_when_synced=True,
copyable=True,
),
'postcode': univention.admin.property(
short_description=_('Postal code'),
long_description='',
syntax=univention.admin.syntax.OneThirdString,
readonly_when_synced=True,
copyable=True,
),
'postOfficeBox': univention.admin.property(
short_description=_('Post office box'),
long_description='',
syntax=univention.admin.syntax.string,
multivalue=True,
copyable=True,
),
'preferredLanguage': univention.admin.property(
short_description=_('Preferred language'),
long_description='',
syntax=univention.admin.syntax.string,
copyable=True,
),
'city': univention.admin.property(
short_description=_('City'),
long_description='',
syntax=univention.admin.syntax.TwoThirdsString,
readonly_when_synced=True,
copyable=True,
),
'country': univention.admin.property(
short_description=_('Country'),
long_description='',
syntax=univention.admin.syntax.Country,
readonly_when_synced=True,
copyable=True,
),
'homeTelephoneNumber': univention.admin.property(
short_description=_('Private telephone number'),
long_description='',
syntax=univention.admin.syntax.phone,
multivalue=True,
readonly_when_synced=True,
copyable=True,
),
'mobileTelephoneNumber': univention.admin.property(
short_description=_('Mobile phone number'),
long_description='',
syntax=univention.admin.syntax.phone,
multivalue=True,
readonly_when_synced=True,
copyable=True,
),
'pagerTelephoneNumber': univention.admin.property(
short_description=_('Pager telephone number'),
long_description='',
syntax=univention.admin.syntax.phone,
multivalue=True,
readonly_when_synced=True,
copyable=True,
),
'homePostalAddress': univention.admin.property(
short_description=_('Private postal address'),
long_description='',
syntax=univention.admin.syntax.postalAddress,
multivalue=True,
copyable=True,
),
'preferredDeliveryMethod': univention.admin.property(
short_description=_('Preferred delivery method'),
long_description='',
syntax=univention.admin.syntax.string,
),
'physicalDeliveryOfficeName': univention.admin.property(
short_description=_('Delivery office name'),
long_description='',
syntax=univention.admin.syntax.string,
copyable=True,
),
}
layout = [
Tab(_('General'), _('Basic settings'), layout=[
Group(_('User account'), layout=[
['title', 'firstname', 'lastname'],
['description'],
]),
Group(_('Personal information'), layout=[
'displayName',
'birthday',
'jpegPhoto',
]),
Group(_('Organisation'), layout=[
'organisation',
['employeeNumber', 'employeeType'],
'secretary',
]),
]),
Tab(_('Contact'), _('Contact information'), layout=[
Group(_('Business'), layout=[
'e-mail',
'phone',
['roomNumber', 'departmentNumber'],
['street', 'postcode', 'city'],
['country'],
]),
Group(_('Private'), layout=[
'homeTelephoneNumber',
'mobileTelephoneNumber',
'pagerTelephoneNumber',
'homePostalAddress'
]),
]),
]
mapping = univention.admin.mapping.mapping()
mapping.register('cn', 'cn', None, univention.admin.mapping.ListToString)
mapping.register('lastname', 'sn', None, univention.admin.mapping.ListToString)
mapping.register('firstname', 'givenName', None, univention.admin.mapping.ListToString)
mapping.register('title', 'title', None, univention.admin.mapping.ListToString)
mapping.register('description', 'description', None, univention.admin.mapping.ListToString)
mapping.register('displayName', 'displayName', None, univention.admin.mapping.ListToString)
mapping.register('birthday', 'univentionBirthday', None, univention.admin.mapping.ListToString)
mapping.register('jpegPhoto', 'jpegPhoto', univention.admin.mapping.mapBase64, univention.admin.mapping.unmapBase64)
mapping.register('organisation', 'o', None, univention.admin.mapping.ListToString)
mapping.register('employeeNumber', 'employeeNumber', None, univention.admin.mapping.ListToString)
mapping.register('employeeType', 'employeeType', None, univention.admin.mapping.ListToString)
mapping.register('secretary', 'secretary')
mapping.register('e-mail', 'mail', encoding='ASCII')
mapping.register('preferredLanguage', 'preferredLanguage', None, univention.admin.mapping.ListToString)
mapping.register('preferredDeliveryMethod', 'preferredDeliveryMethod', None, univention.admin.mapping.ListToString)
mapping.register('phone', 'telephoneNumber')
mapping.register('roomNumber', 'roomNumber')
mapping.register('departmentNumber', 'departmentNumber')
mapping.register('physicalDeliveryOfficeName', 'physicalDeliveryOfficeName', None, univention.admin.mapping.ListToString)
mapping.register('street', 'street', None, univention.admin.mapping.ListToString)
mapping.register('postcode', 'postalCode', None, univention.admin.mapping.ListToString)
mapping.register('postOfficeBox', 'postOfficeBox')
mapping.register('city', 'l', None, univention.admin.mapping.ListToString)
mapping.register('country', 'st', None, univention.admin.mapping.ListToString)
mapping.register('homeTelephoneNumber', 'homePhone')
mapping.register('mobileTelephoneNumber', 'mobile')
mapping.register('pagerTelephoneNumber', 'pager')
mapping.register('homePostalAddress', 'homePostalAddress', mapHomePostalAddress, unmapHomePostalAddress)
[docs]class object(univention.admin.handlers.simpleLdap):
module = module
[docs] def description(self):
description = '%s %s' % (self['firstname'] or '', self['lastname'])
return description.strip()
[docs] def get_candidate_dn(self):
dn = self._ldap_dn()
if self.exists():
rdn = self.lo.explodeDn(dn)[0]
dn = u'%s,%s' % (rdn, self.lo.parentDn(self.dn))
return dn
[docs] def unique_dn(self):
candidate_dn = self.get_candidate_dn()
try:
self.lo.searchDn(base=candidate_dn, scope='base')
except univention.admin.uexceptions.noObject:
return True
else:
return False
[docs] def acquire_unique_dn(self):
nonce = 1
cn = u'%s %s %d' % (self['firstname'] or '', self['lastname'], nonce,)
self['cn'] = cn.strip()
while not self.unique_dn():
nonce += 1
cn = u'%s %s %d' % (self['firstname'] or '', self['lastname'], nonce,)
self['cn'] = cn.strip()
return self.get_candidate_dn()
def _ldap_pre_ready(self):
super(object, self)._ldap_pre_ready()
if not self.exists() or self.hasChanged(('firstname', 'lastname',)):
self.acquire_unique_dn()
def _ldap_modlist(self):
ml = univention.admin.handlers.simpleLdap._ldap_modlist(self)
ml = self._modlist_display_name(ml)
ml = self._modlist_univention_person(ml)
return ml
def _modlist_display_name(self, ml):
# update displayName automatically if no custom value has been entered by the user and the name changed
if self.info.get('displayName') == self.oldinfo.get('displayName') and (self.info.get('firstname') != self.oldinfo.get('firstname') or self.info.get('lastname') != self.oldinfo.get('lastname')):
prop_displayName = self.descriptions['displayName']
old_default_displayName = prop_displayName._replace(prop_displayName.base_default, self.oldinfo)
# does old displayName match with old default displayName?
if self.oldinfo.get('displayName', '') == old_default_displayName:
# yes ==> update displayName automatically
new_displayName = prop_displayName._replace(prop_displayName.base_default, self)
ml.append(('displayName', self.oldattr.get('displayName', [b''])[0], new_displayName.encode('UTF-8')))
return ml
def _modlist_univention_person(self, ml):
if self.hasChanged('birthday'):
# make sure that univentionPerson is set as objectClass when birthday is set
if self['birthday'] and b'univentionPerson' not in self.oldattr.get('objectClass', []):
ml.append(('objectClass', b'', b'univentionPerson'))
# remove univentionPerson as objectClass when birthday is unset
elif not self['birthday'] and b'univentionPerson' in self.oldattr.get('objectClass', []):
ml.append(('objectClass', b'univentionPerson', b''))
return ml
def _move(self, newdn, modify_childs=True, ignore_license=False):
olddn = self.dn
# acquire unique dn in new position
self.dn = newdn
newdn = self.acquire_unique_dn()
self.dn = olddn
self.lo.rename(self.dn, newdn)
self.dn = newdn
try:
self._move_in_groups(olddn) # can be done always, will do nothing if oldinfo has no attribute 'groups'
self._move_in_subordinates(olddn)
self._ldap_post_move(olddn)
except BaseException:
# move back
ud.debug(ud.ADMIN, ud.WARN, 'simpleLdap._move: self._ldap_post_move failed, move object back to %s' % olddn)
self.lo.rename(self.dn, olddn)
self.dn = olddn
raise
return self.dn
[docs] @classmethod
def unmapped_lookup_filter(cls):
return univention.admin.filter.conjunction('&', [
univention.admin.filter.expression('objectClass', 'person'),
univention.admin.filter.expression('objectClass', 'inetOrgPerson'),
univention.admin.filter.expression('objectClass', 'organizationalPerson'),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'posixAccount')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'shadowAccount')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'sambaSamAccount')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'krb5Principal')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'krb5KDCEntry')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'univentionMail')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'simpleSecurityObject')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'uidObject')]),
univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'pkiUser')]),
])
lookup = object.lookup
lookup_filter = object.lookup_filter
[docs]def identify(dn, attr, canonical=False):
# FIXME is this if block needed? copy pasted from users/user
if b'0' in attr.get('uidNumber', []) or b'$' in attr.get('uid', [b''])[0] or b'univentionHost' in attr.get('objectClass', []) or b'functional' in attr.get('univentionObjectFlag', []):
return False
required_ocs = {b'person', b'inetOrgPerson', b'organizationalPerson', }
forbidden_ocs = {b'posixAccount', b'shadowAccount', b'sambaSamAccount', b'krb5Principal', b'krb5KDCEntry', b'univentionMail', b'simpleSecurityObject', b'uidObject', b'pkiUser', }
ocs = set(attr.get('objectClass', []))
return (ocs & required_ocs == required_ocs) and not (ocs & forbidden_ocs)