Source code for univention.admin.handlers.settings.portal

# -*- coding: utf-8 -*-
#
# Copyright 2017-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 Univention Portal
"""

import json
from ldap.filter import filter_format

from univention.admin.layout import Tab, Group
import univention.admin.filter
import univention.admin.localization

translation = univention.admin.localization.translation('univention.admin.handlers.settings')
_ = translation.translate

module = 'settings/portal'
superordinate = 'settings/cn'
default_containers = ['cn=portal,cn=univention']
childs = False
operations = ['add', 'edit', 'remove', 'search', 'move']
short_description = _('Portal: Portal')
object_name = _('Portal')
object_name_plural = _('Portals')
long_description = _('Object that feeds everything in https://fqdn/univention/portal')
options = {
	'default': univention.admin.option(
		short_description=short_description,
		default=True,
		objectClasses=['top', 'univentionPortal'],
	),
}
property_descriptions = {
	'name': univention.admin.property(
		short_description=_('Internal name'),
		long_description='',
		syntax=univention.admin.syntax.string_numbers_letters_dots,
		include_in_default_search=True,
		required=True,
		may_change=False,
		identifies=True
	),
	'displayName': univention.admin.property(
		short_description=_('Display Name'),
		long_description=_('Headline of the portal. At least one entry; strongly encouraged to have one for en_US'),
		syntax=univention.admin.syntax.LocalizedDisplayName,
		multivalue=True,
		required=True,
	),
	'showMenu': univention.admin.property(
		short_description=_('Show menu'),
		long_description='',
		syntax=univention.admin.syntax.TrueFalseUp,
		default='TRUE',
	),
	'showSearch': univention.admin.property(
		short_description=_('Show search'),
		long_description='',
		syntax=univention.admin.syntax.TrueFalseUp,
		default='TRUE',
	),
	'showLogin': univention.admin.property(
		short_description=_('Show login'),
		long_description='',
		syntax=univention.admin.syntax.TrueFalseUp,
		default='TRUE',
	),
	'showApps': univention.admin.property(
		short_description=_('Show apps'),
		long_description=_('Shows links to locally installed Apps'),
		syntax=univention.admin.syntax.TrueFalseUp,
		default='TRUE',
	),
	'showServers': univention.admin.property(
		short_description=_('Show servers'),
		long_description=_('Shows links to all UCS servers'),
		syntax=univention.admin.syntax.TrueFalseUp,
		default='TRUE',
	),
	'background': univention.admin.property(
		short_description=_('Background'),
		long_description=_('Background image of the Portal'),
		syntax=univention.admin.syntax.Base64BaseUpload,
		dontsearch=True,
	),
	'logo': univention.admin.property(
		short_description=_('Portal logo'),
		long_description=_('Logo image for the portal.'),
		syntax=univention.admin.syntax.Base64BaseUpload,
		dontsearch=True,
	),
	'cssBackground': univention.admin.property(
		short_description=_('CSS background'),
		long_description=_("Style definition for the CSS 'background' property which will be applied to the portal page, e.g. linear-gradient(black, white)"),
		syntax=univention.admin.syntax.TwoString,
		dontsearch=True,
	),
	'fontColor': univention.admin.property(
		short_description=_('Font color'),
		long_description=_('Defines the color which is used for the fonts on the portal page as well as the icons in the header.'),
		syntax=univention.admin.syntax.PortalFontColor,
		default='black',
		dontsearch=True,
	),
	'portalComputers': univention.admin.property(
		short_description=_('Show on server'),
		long_description=_('This portal will be used as start site for the given server'),
		syntax=univention.admin.syntax.PortalComputer,
		multivalue=True,
		dontsearch=True,
	),
	'ensureLogin': univention.admin.property(
		short_description=_('Redirect anonymous visitors to the login'),
		syntax=univention.admin.syntax.TrueFalseUp,
		default='FALSE',
		dontsearch=True,
	),
	'anonymousEmpty': univention.admin.property(
		syntax=univention.admin.syntax.LocalizedAnonymousEmpty,
		multivalue=True,
		dontsearch=True,
	),
	'autoLayoutCategories': univention.admin.property(
		short_description=_('The categories are displayed side by side if there is enough space'),
		syntax=univention.admin.syntax.TrueFalseUp,
		default='FALSE',
		dontsearch=True,
	),
	# 'portalEntriesOrder' - deprecated by 'content' of settings/portal
	'portalEntriesOrder': univention.admin.property(
		short_description=_('Portal entries order'),
		long_description=_('The order in which the portal entries are shown on this portal'),
		syntax=univention.admin.syntax.PortalEntries,
		multivalue=True,
	),
	'links': univention.admin.property(
		short_description=_('Portal links'),
		long_description=_('List of static links shown on this portal. Only those links for the selected locale are shown (e.g.,: en_US, de_DE).'),
		syntax=univention.admin.syntax.PortalLinks,
		multivalue=True,
	),
	'content': univention.admin.property(
		short_description=_('Portal content'),
		syntax=univention.admin.syntax.PortalCategorySelection,
	),
	'defaultLinkTarget': univention.admin.property(
		short_description=_('Default browser tab for portal entries'),
		syntax=univention.admin.syntax.PortalDefaultLinkTarget,
		default='samewindow',
		dontsearch=True,
	),
}

layout = [
	Tab(_('General'), _('Portal options'), layout=[
		Group(_('Name'), layout=[
			['name'],
			['displayName'],
		]),
		Group(_('Visibility'), layout=[
			['portalComputers'],
		]),
		Group(_('Appearance'), layout=[
			['logo'],
			['background'],
			['cssBackground'],
			['fontColor'],
			['autoLayoutCategories'],
		]),
		Group(_('General Content'), layout=[
			# ["showMenu"],
			# ["showSearch"],
			# ["showLogin"],
			['defaultLinkTarget'],
			['showApps'],
			# ["showServers"],
			['links'],
		]),
	]),
	Tab(_('Portal categories and entries'), _('The categories and entries that are shown on this portal'), layout=[
		['content'],
	]),
	Tab(_('Manage anonymous visitors'), _('Manage anonymous visitors'), layout=[
		Group(_('Login'), layout=[
			['ensureLogin'],
		]),
		Group(_('Message when the portal is empty'), layout=[
			['anonymousEmpty'],
		]),
	]),
]


[docs]def mapLinkValue(vals, encoding=()): return [u'$$'.join(val).encode(*encoding) for val in vals]
[docs]def unmapLinkValue(vals, encoding=()): return [val.decode(*encoding).split(u'$$', 3) for val in vals]
[docs]def mapTranslationValue(vals, encoding=()): return [u' '.join(val).encode(*encoding) for val in vals]
[docs]def unmapTranslationValue(vals, encoding=()): return [val.decode(*encoding).split(u' ', 1) for val in vals]
[docs]def mapContent(vals, encoding=()): return json.dumps(vals).encode(*encoding)
[docs]def unmapContent(vals, encoding=()): return json.loads(vals[0].decode(*encoding))
mapping = univention.admin.mapping.mapping() mapping.register('name', 'cn', None, univention.admin.mapping.ListToString) mapping.register('displayName', 'univentionPortalDisplayName', mapTranslationValue, unmapTranslationValue) mapping.register('showMenu', 'univentionPortalShowMenu', None, univention.admin.mapping.ListToString) mapping.register('showSearch', 'univentionPortalShowSearch', None, univention.admin.mapping.ListToString) mapping.register('showLogin', 'univentionPortalShowLogin', None, univention.admin.mapping.ListToString) mapping.register('showApps', 'univentionPortalShowApps', None, univention.admin.mapping.ListToString) mapping.register('showServers', 'univentionPortalShowServers', None, univention.admin.mapping.ListToString) mapping.register('ensureLogin', 'univentionPortalEnsureLogin', None, univention.admin.mapping.ListToString) mapping.register('anonymousEmpty', 'univentionPortalAnonymousEmpty', mapTranslationValue, unmapTranslationValue) mapping.register('autoLayoutCategories', 'univentionPortalAutoLayoutCategories', None, univention.admin.mapping.ListToString) mapping.register('background', 'univentionPortalBackground', None, univention.admin.mapping.ListToString) mapping.register('cssBackground', 'univentionPortalCSSBackground', None, univention.admin.mapping.ListToString) mapping.register('fontColor', 'univentionPortalFontColor', None, univention.admin.mapping.ListToString) mapping.register('logo', 'univentionPortalLogo', None, univention.admin.mapping.ListToString) mapping.register('portalEntriesOrder', 'univentionPortalEntriesOrder') mapping.register('links', 'univentionPortalLinks', mapLinkValue, unmapLinkValue) mapping.register('content', 'univentionPortalContent', mapContent, unmapContent) mapping.register('defaultLinkTarget', 'univentionPortalDefaultLinkTarget', None, univention.admin.mapping.ListToString)
[docs]class object(univention.admin.handlers.simpleLdap): module = module
[docs] def open(self): super(object, self).open() if self.exists(): self['portalComputers'] = self.lo.searchDn(filter=filter_format('(&(objectClass=univentionPortalComputer)(univentionComputerPortal=%s))', [self.dn])) self.save()
def _ldap_pre_create(self): super(object, self)._ldap_pre_create() self.__update_deprecated_property__portal_entries_order__of__self() def _ldap_pre_modify(self): super(object, self)._ldap_pre_modify() self.__update_deprecated_property__portal_entries_order__of__self() def _ldap_post_create(self): super(object, self)._ldap_post_create() self.__update_portal_computers() self.__update_deprecated_property__portal__of__portal_entry() def _ldap_post_modify(self): super(object, self)._ldap_post_modify() self.__update_portal_computers() self.__update_deprecated_property__portal__of__portal_entry() def __update_deprecated_property__portal_entries_order__of__self(self): # Use the order of the settings/portal_entry objects in the 'content' property # for the deprecated 'portalEntriesOrder' property. # Be aware that 'portalEntriesOrder' will not get updated if # the settings/portal_entry objects are the same but only ordering changes. # ['A', 'B', 'C'] # ['B', 'A', 'C'] # this will be ignored, since only ordering changed # This was previously bypassed by unsetting 'portalEntriesOrder' and then resetting with the new order if self.hasChanged('content'): content = self.info.get('content', []) # Workaround for Bug #47872 - Comment #1 if (len(content) == 1 and len(content[0][1]) == 1 and content[0][0].startswith('cn=admin,') and content[0][1][0].startswith('cn=univentionblog,')): return new_order = [entry for category, entries in content for entry in entries] new_order_no_duplicates = [] for entry in new_order: if entry not in new_order_no_duplicates: new_order_no_duplicates.append(entry) self['portalEntriesOrder'] = new_order_no_duplicates def __update_portal_computers(self): if self.exists(): # case coming from _ldap_post_modify old_portal_computers = self.oldinfo.get('portalComputers', []) else: # case coming from _ldap_post_create old_portal_computers = [] new_portal_computers = self.info.get('portalComputers', []) # set portal attribute of old computers to blank for computer in old_portal_computers: if computer not in new_portal_computers: try: compobj = univention.admin.modules.lookup('computers/computer', None, self.lo, scope='base', base=computer)[0] # initialize module of the computer obj for extended attributes compmod = univention.admin.modules.get(compobj.module) if not compmod.initialized: univention.admin.modules.init(self.lo, self.position, compmod) compobj = univention.admin.modules.lookup('computers/computer', None, self.lo, scope='base', base=computer)[0] except univention.admin.uexceptions.noObject: continue compobj.open() compobj['portal'] = '' compobj.modify() # set portal attribute of new computers to this portal for computer in new_portal_computers: if computer not in old_portal_computers: try: compobj = univention.admin.modules.lookup('computers/computer', None, self.lo, scope='base', base=computer)[0] # initialize module of the computer obj for extended attributes compmod = univention.admin.modules.get(compobj.module) if not compmod.initialized: univention.admin.modules.init(self.lo, self.position, compmod) compobj = univention.admin.modules.lookup('computers/computer', None, self.lo, scope='base', base=computer)[0] except univention.admin.uexceptions.noObject: continue compobj.open() compobj['portal'] = self.dn compobj.modify() def __update_deprecated_property__portal__of__portal_entry(self): # Remove this portal from the 'portal' property of settings/portal_entry objects # if they were removed from the 'content' property. # Add this portal if they were added to 'content'. if not self.hasChanged('content'): return portal_entry_mod = univention.admin.modules.get('settings/portal_entry') old_content = self.oldinfo.get('content', []) old_entries = [entry for category, entries in old_content for entry in entries] new_content = self.info.get('content', []) new_entries = [entry for category, entries in new_content for entry in entries] # remove this portal from removed entries removed_entries = [entry for entry in old_entries if entry not in new_entries] for entry_dn in removed_entries: try: entry_obj = univention.admin.objects.get(portal_entry_mod, None, self.lo, position='', dn=entry_dn) except univention.admin.uexceptions.noObject: continue else: entry_obj.open() old_portal = entry_obj.info.get('portal', []) new_portal = [portal for portal in old_portal if not self.lo.compare_dn(portal, self.dn)] if new_portal != old_portal: entry_obj['portal'] = new_portal entry_obj.modify() # add this portal to added entries added_entries = [entry for entry in new_entries if entry not in old_entries] for entry_dn in added_entries: try: entry_obj = univention.admin.objects.get(portal_entry_mod, None, self.lo, position='', dn=entry_dn) except univention.admin.uexceptions.noObject: continue else: entry_obj.open() old_portal = entry_obj.info.get('portal', []) new_portal = old_portal + ([self.dn] if self.dn not in old_portal else []) if old_portal != new_portal: entry_obj['portal'] = new_portal entry_obj.modify() def _ldap_post_remove(self): super(object, self)._ldap_post_remove() for obj in univention.admin.modules.lookup('settings/portal_entry', None, self.lo, scope='sub', filter=filter_format('portal=%s', [self.dn])): obj.open() obj['portal'] = [x for x in obj.info.get('portal', []) if not self.lo.compare_dn(x, self.dn)] obj.modify() def _ldap_post_move(self, olddn): super(object, self)._ldap_post_move(olddn) for obj in univention.admin.modules.lookup('settings/portal_entry', None, self.lo, scope='sub', filter=filter_format('portal=%s', [olddn])): obj.open() obj['portal'] = [x for x in obj.info.get('portal', []) + [self.dn] if not self.lo.compare_dn(x, olddn)] obj.modify()
lookup = object.lookup lookup_filter = object.lookup_filter identify = object.identify