- [kopano](src/kopano/plugin/movetopublicldap.py) Add sample config file for the kopano-dagent python plugin movetopublicldap.

master v1.2.7
mlan 2021-01-08 18:09:55 +01:00
parent b9c2529745
commit 6e32101634
8 changed files with 90 additions and 42 deletions

View File

@ -1,6 +1,7 @@
# 1.2.7 # 1.2.7
- [kopano](src/kopano/plugin/movetopublicldap.py) Add LDAP support to the [Move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public ) kopano-dagent python plugin. - [kopano](src/kopano/plugin/movetopublicldap.py) Add LDAP support to the [Move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public ) kopano-dagent python plugin.
- [kopano](src/kopano/plugin/movetopublicldap.py) Add sample config file for the kopano-dagent python plugin movetopublicldap.
# 1.2.6 # 1.2.6

View File

@ -444,13 +444,13 @@ The [Mobile Device Management](https://documentation.kopano.io/webapp_mdm_manual
There are two type of stores (folders containing communication elements); private and public stores. There can only be one public store. It is the Kopano dagent that places incoming messages into mail boxes, that is the private and public stores. There are two type of stores (folders containing communication elements); private and public stores. There can only be one public store. It is the Kopano dagent that places incoming messages into mail boxes, that is the private and public stores.
Public folders are managed by the system admin and not by individual users. Users have them mapped automatically. The public folders can be synced via [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) . Public folders are configured by the system admin. Users have them mapped automatically. The shared and public folders can be synced with mobile devices via [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only). Each user can manage which shared or public folders to sync with her mobile devices by using the [Mobile Device Management](#mobile-device-management) WebApp plugin.
With the current Kopano implementation, delivering to the public store is configured separately from normal user management. There is the [move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public) plugin which moves incoming messages to a folder in the public store. It has a static configuration and does not support LDAP lookup. With the current Kopano implementation, delivering to the public store is configured separately from normal user management. There is the [move to public](https://documentation.kopano.io/kopanocore_administrator_manual/special_kc_configurations.html#move-to-public) plugin which moves incoming messages to a folder in the public store. It has a static configuration and does not support LDAP lookup.
### Move to public with LDAP lookup ### Move to public with LDAP lookup
The `mlan/kopano` image include a extended version of the move to public plugin which use LDAP lookup, instead of a static file based lookup. When the plugin [move to public with LDAP](src/kopano/plugin/movetopublicldap.py) is enabled, `DAGENT_PLUGINS=movetopublicldap.py`, the kopano dagent will do two LDAP queries. The first is to search for an entry/user with matching email address. The second, introduced with this plugin, is to get the public folder from this entry. If found the message will be delivered to the public folder otherwise it will be delivered to the mailbox of the user. The `mlan/kopano` image include a extended version of the move to public plugin which use LDAP lookup, instead of a static file based lookup. When the plugin [move to public with LDAP](src/kopano/plugin/movetopublicldap.py) is enabled, `DAGENT_PLUGINS=movetopublicldap`, the kopano dagent will do two LDAP queries. The first is to search for an entry/user with matching email address. The second, introduced with this plugin, is to get the public folder from this entry. If found the message will be delivered to the public folder otherwise it will be delivered to the mailbox of the user.
Lets demonstrate how delivery to a public folder is configured. With this LDAP entry: Lets demonstrate how delivery to a public folder is configured. With this LDAP entry:
@ -466,11 +466,11 @@ mail: public@example.com
kopanoAccount: 1 kopanoAccount: 1
kopanoHidden: 1 kopanoHidden: 1
kopanoSharedStoreOnly: 1 kopanoSharedStoreOnly: 1
kopanoResourceType: publicStore:Public Stores/public kopanoResourceType: publicFolder:Public Stores/public
``` ```
messages to `public@example.com` will be delivered to the public store in `Public Stores/public`. messages to `public@example.com` will be delivered to the public store in `Public Stores/public`.
The central [attribute](https://documentation.kopano.io/kopanocore_administrator_manual/appendix_b.html#appendix-b-ldap-attribute-description) is `kopanoResourceType: publicStore:Public Stores/public`. It contains a token and a folder name. The token match is case sensitive and there must be a colon `:` separating The central [attribute](https://documentation.kopano.io/kopanocore_administrator_manual/appendix_b.html#appendix-b-ldap-attribute-description) is `kopanoResourceType: publicFolder:Public Stores/public`. It contains a token and a folder name. The token match is case sensitive and there must be a colon `:` separating
the token and the public folder name. The folder name can contain space and the token and the public folder name. The folder name can contain space and
sub folders, which are distinguished using a forward slash `/`. sub folders, which are distinguished using a forward slash `/`.
@ -479,17 +479,17 @@ The LDAP attribute holding the token and the token itself have the following
default values, which can be modified in `/etc/kopano/movetopublicldap.cfg` if desired. default values, which can be modified in `/etc/kopano/movetopublicldap.cfg` if desired.
```yaml ```yaml
ldap_public_store_attribute = kopanoResourceType ldap_public_folder_attribute = kopanoResourceType
ldap_public_store_attribute_token = publicStore ldap_public_folder_attribute_token = publicFolder
``` ```
As with other parameters, environment variables can be used to define them: `LDAP_PUBLIC_STORE_ATTRIBUTE=kopanoResourceType` and `LDAP_PUBLIC_STORE_ATTRIBUTE_TOKEN=publicStore`. As with other parameters, environment variables can be used to define them: `LDAP_PUBLIC_FOLDER_ATTRIBUTE=kopanoResourceType` and `LDAP_PUBLIC_FOLDER_ATTRIBUTE_TOKEN=publicFolder`.
## Shared folders ## Shared folders
Users can share folders when sufficient permission have been granted. When logged into WebApp with an administrative account (`kopanoAdmin: 1`) you can modify the permissions on users shares and folders. Users can then, when logged into WebApp, open the inbox of other users by selecting `Open Shared Mails`. Users can share folders when sufficient permission have been granted. When logged into WebApp with an administrative account (`kopanoAdmin: 1`) you can modify the permissions on users shares and folders. Users can then, when logged into WebApp, open the inbox of other users by selecting `Open Shared Mails`.
The [impersonation](https://wiki.z-hub.io/display/ZP/Impersonation) mechanism allow such shared folders to be synced over [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) too. The [impersonation](https://wiki.z-hub.io/display/ZP/Impersonation) mechanism allow such shared folders to be synced over [Exchange ActiveSync (EAS)](https://wiki.z-hub.io/display/ZP/Sharing+folders+and+Read-only) too. Each user can manage which shared or public folders to sync with her mobile devices by using the [Mobile Device Management](#mobile-device-management) WebApp plugin.
## Crontab ## Crontab

View File

@ -179,7 +179,7 @@ mail: $(LDAP_TEST_PUB)@$(MAIL_DOMAIN)
kopanoAccount: 1 kopanoAccount: 1
kopanoHidden: 1 kopanoHidden: 1
kopanoSharedStoreOnly: 1 kopanoSharedStoreOnly: 1
kopanoResourceType: publicStore:Public Stores/public kopanoResourceType: publicFolder:Public Stores/public
endef endef
export LDIF_ADD_STO export LDIF_ADD_STO

View File

@ -27,7 +27,7 @@ services:
- LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${LDAP_GROUPOBJ-posixGroup} - LDAP_GROUP_TYPE_ATTRIBUTE_VALUE=${LDAP_GROUPOBJ-posixGroup}
- LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn - LDAP_GROUPMEMBERS_ATTRIBUTE_TYPE=dn
- LDAP_PROPMAP= - LDAP_PROPMAP=
- DAGENT_PLUGINS=movetopublicldap.py - DAGENT_PLUGINS=movetopublicldap
- MYSQL_DATABASE=${MYSQL_DATABASE-kopano} - MYSQL_DATABASE=${MYSQL_DATABASE-kopano}
- MYSQL_USER=${MYSQL_USER-kopano} - MYSQL_USER=${MYSQL_USER-kopano}
- MYSQL_PASSWORD=${MYSQL_PASSWORD-secret} - MYSQL_PASSWORD=${MYSQL_PASSWORD-secret}

View File

@ -18,7 +18,7 @@ DOCKER_MAN5_DIR=${DOCKER_MAN5_DIR-/usr/share/man/man5/}
DOCKER_APPL_SSL_CERT=${DOCKER_APPL_SSL_CERT-$DOCKER_APPL_SSL_DIR/cert.pem} DOCKER_APPL_SSL_CERT=${DOCKER_APPL_SSL_CERT-$DOCKER_APPL_SSL_DIR/cert.pem}
DOCKER_APPL_SSL_KEY=${DOCKER_APPL_SSL_KEY-$DOCKER_APPL_SSL_DIR/priv_key.pem} DOCKER_APPL_SSL_KEY=${DOCKER_APPL_SSL_KEY-$DOCKER_APPL_SSL_DIR/priv_key.pem}
DOCKER_LDAP_PMAP_FILE=${DOCKER_LDAP_PMAP_FILE-/usr/share/kopano/ldap.propmap.cfg} DOCKER_LDAP_PMAP_FILE=${DOCKER_LDAP_PMAP_FILE-/usr/share/kopano/ldap.propmap.cfg}
DOCKER_LDAP_SERVICES=${DOCKER_LDAP_SERVICES-archiver dagent gateway ical ldap movetopublicldap search server spamd spooler} DOCKER_LDAP_SERVICES=${DOCKER_LDAP_SERVICES-archiver dagent gateway ical ldap search server spamd spooler}
sqlstate_cfg_file=$DOCKER_CONF_DIR2/backend/sqlstatemachine/config.php sqlstate_cfg_file=$DOCKER_CONF_DIR2/backend/sqlstatemachine/config.php
zpush_cfg_file=$DOCKER_CONF_DIR2/config.php zpush_cfg_file=$DOCKER_CONF_DIR2/config.php
webapp_cfg_file=$DOCKER_CONF_DIR1/webapp/config.php webapp_cfg_file=$DOCKER_CONF_DIR1/webapp/config.php
@ -30,7 +30,7 @@ webapp_smime_cfg_file=$DOCKER_CONF_DIR1/webapp/config-smime.php
# CLI commands # CLI commands
# #
list_parms() { list_parms() {
local services="$DOCKER_LDAP_SERVICES" local services="$DOCKER_LDAP_SERVICES $DAGENT_PLUGINS"
[ $# -ge 0 ] && services="$@" [ $# -ge 0 ] && services="$@"
for service in $services; do for service in $services; do
[ $# -ne 1 ] && echo "# $service" [ $# -ne 1 ] && echo "# $service"
@ -50,7 +50,7 @@ list_parms() {
# #
kopano_apply_envvars_core() { kopano_apply_envvars_core() {
kopano_enable_envvars_plugin kopano_enable_envvars_plugin
for service in $DOCKER_LDAP_SERVICES; do for service in $DOCKER_LDAP_SERVICES $DAGENT_PLUGINS; do
kopano_apply_envvars_cfg $service kopano_apply_envvars_cfg $service
done done
} }
@ -130,7 +130,7 @@ kopano_get_envvars_ext() {
local vars local vars
case $service in case $service in
movetopublicldap) movetopublicldap)
vars="ldap_public_store_attribute ldap_public_store_attribute_token" vars="ldap_public_folder_attribute ldap_public_folder_attribute_token"
;; ;;
esac esac
echo $vars | tr "[:lower:]" "[:upper:]" | tr " " "\n" | sort -u echo $vars | tr "[:lower:]" "[:upper:]" | tr " " "\n" | sort -u
@ -143,9 +143,16 @@ kopano_enable_envvars_plugin() {
for service in dagent spooler; do for service in dagent spooler; do
local envvar=${service^^}_PLUGINS local envvar=${service^^}_PLUGINS
for plugin in ${!envvar}; do for plugin in ${!envvar}; do
dc_log 5 "Enabling ${service} python plugin ${plugin}" local pyfile=$(find /usr/share/kopano* -name ${plugin}.py)
ln -sf /usr/share/kopano-${service}/python/plugins/${plugin} /var/lib/kopano/${service}/plugins if [ -z "$pyfile" ]; then
export ${service^^}_PLUGIN_ENABLED=yes dc_log 5 "Unable to locate ${service} python plugin ${plugin}"
else
dc_log 5 "Enabling ${service} python plugin ${plugin}"
mkdir -p $DOCKER_CONF_DIR1/${service}
ln -sf ${pyfile} $DOCKER_CONF_DIR1/${service}
export ${service^^}_PLUGIN_PATH=$DOCKER_CONF_DIR1/${service}
export ${service^^}_PLUGIN_ENABLED=yes
fi
done done
done done
} }

View File

@ -0,0 +1,32 @@
# movetopublicldap.cfg
#
# Move mail to public ldap configuration
#
# This configuration file is used by the dagent plugin 'movetopublicldap.py'
# The default location is '/etc/kopano/movetopublicldap.cfg'
#
#
# The plugin reads two coniguration files.
# First it looks for parameters in '/etc/kopano/ldap.cfg' allowing
# common conifuration parameters to be kep tin on place.
# Second it reads '/etc/kopano/movetopublicldap.cfg'
#
#
# Parameters that can be set in both files:
#
# ldap_uri =
# ldap_starttls = no
# ldap_search_base =
# ldap_bind_user =
# ldap_bind_passwd =
# ldap_user_unique_attribute = uid
#
#
# Parameters that can only be set in ovetopublicldap.cfg:
#
# ldap_public_folder_attribute = kopanoResourceType
# ldap_public_folder_attribute_token = publicFolder
#

View File

@ -8,13 +8,13 @@ store. If folders are missing they will be created.
A LDAP entry including: A LDAP entry including:
kopanoAccount: 1 kopanoAccount: 1
kopanoResourceType: publicStore:<public folder> kopanoResourceType: publicFolder:<public folder>
will have its email delivered to the public store in <public folder>. will have its email delivered to the public store in <public folder>.
The token match is case sensitive and there must be a colon ':' separating The token match is case sensitive and there must be a colon ':' separating
the token and the public folder name. The folder name can contain space and the token and the public folder name. The folder name can contain space and
sub folders, which are distinguished using forward slash '/'. sub folders, which are distinguished using forward slash '/'.
So if we have 'kopanoResourceType: publicStore:Public Stores/public' emails will So if we have 'kopanoResourceType: publicFolder:Public Stores/public' emails will
be delivered to 'Public Folders/Public Stores/public'. be delivered to 'Public Folders/Public Stores/public'.
The parameters in /etc/kopano/ldap.cfg will be used for the LDAP query. The parameters in /etc/kopano/ldap.cfg will be used for the LDAP query.
@ -22,8 +22,8 @@ The LDAP attribute holding the token and the token itself have the following
default values, which can be modified in /etc/kopano/movetopublicldap.cfg default values, which can be modified in /etc/kopano/movetopublicldap.cfg
if desired. if desired.
ldap_public_store_attribute = kopanoResourceType ldap_public_folder_attribute = kopanoResourceType
ldap_public_store_attribute_token = publicStore ldap_public_folder_attribute_token = publicFolder
""" """
from sys import hexversion from sys import hexversion
@ -37,6 +37,7 @@ from plugintemplates import IMapiDAgentPlugin, MP_CONTINUE, MP_STOP_SUCCESS
from zconfig import ZConfigParser from zconfig import ZConfigParser
import configparser import configparser
import ldap import ldap
import os.path
class KConfigParser(ZConfigParser): class KConfigParser(ZConfigParser):
""" Extends zconfig.ZConfigParser to also allow !directive in cfg files """ """ Extends zconfig.ZConfigParser to also allow !directive in cfg files """
@ -55,12 +56,13 @@ class MoveToPublic(IMapiDAgentPlugin):
DEFAULTCONFIG = { DEFAULTCONFIG = {
'ldap_uri': None, 'ldap_uri': None,
'ldap_starttls': "no",
'ldap_search_base': None, 'ldap_search_base': None,
'ldap_bind_user': None, 'ldap_bind_user': None,
'ldap_bind_passwd': None, 'ldap_bind_passwd': None,
'ldap_user_unique_attribute': "uid", 'ldap_user_unique_attribute': "uid",
'ldap_public_store_attribute': "kopanoResourceType", 'ldap_public_folder_attribute': "kopanoResourceType",
'ldap_public_store_attribute_token': "publicStore" 'ldap_public_folder_attribute_token': "publicFolder"
} }
def __init__(self, logger): def __init__(self, logger):
@ -72,34 +74,40 @@ class MoveToPublic(IMapiDAgentPlugin):
options = [opt.split('_', 1)[1] for opt in defaultconfig.keys()] options = [opt.split('_', 1)[1] for opt in defaultconfig.keys()]
config = None config = None
for configfile in configfiles: for configfile in configfiles:
if not config: if os.path.isfile(configfile):
config = KConfigParser(configfile, defaultconfig) self.logger.logDebug("*--- Reading file {}".format(configfile))
if not config:
config = KConfigParser(configfile, defaultconfig)
else:
config = KConfigParser(configfile, config.options())
else: else:
config = KConfigParser(configfile, config.options()) self.logger.logDebug("*--- Can't find file {}".format(configfile))
self.config = config.getdict('ldap',options) self.config = config.getdict('ldap',options)
self.logger.logDebug("*--- Config list {}".format(self.config)) self.logger.logDebug("*--- Config list {}".format(self.config))
return self.config return self.config
def searchfilter(self, recipient): def searchfilter(self, recipient):
""" (&(uid=recipient)(kopanoResourceType=publicStore:*)) """ """ (&(uid=recipient)(kopanoResourceType=publicFolder:*)) """
return ("(&({}={})({}={}:*))" return ("(&({}={})({}={}:*))"
.format(self.config['user_unique_attribute'], .format(self.config['user_unique_attribute'],
recipient, recipient,
self.config['public_store_attribute'], self.config['public_folder_attribute'],
self.config['public_store_attribute_token'])) self.config['public_folder_attribute_token']))
def searchquery(self, recipient): def searchquery(self, recipient):
""" Query a LDAP/AD driectory server to lookup recipient using """ Query a LDAP/AD driectory server to lookup recipient using
search_base and return public_store_attribute search_base and return public_folder_attribute
""" """
if (self.config['uri'] is None): if not self.config or not self.config['uri']:
self.logger.logError(("!--- ldap_uri is not defined." self.logger.logError(("!--- ldap_uri is not defined."
" Please check {}" .format(self.CONFIGFILES[0]))) " Please check {}" .format(self.CONFIGFILES)))
return None return None
else: else:
l = ldap.initialize(self.config['uri']) l = ldap.initialize(self.config['uri'])
try: try:
l.protocol_version = ldap.VERSION3 l.protocol_version = ldap.VERSION3
if self.config['starttls'] == 'yes':
l.start_tls_s()
l.simple_bind_s(self.config['bind_user'] or u'', \ l.simple_bind_s(self.config['bind_user'] or u'', \
self.config['bind_passwd'] or u'') self.config['bind_passwd'] or u'')
except ldap.SERVER_DOWN as e: except ldap.SERVER_DOWN as e:
@ -108,7 +116,7 @@ class MoveToPublic(IMapiDAgentPlugin):
return None return None
except ldap.INVALID_CREDENTIALS as e: except ldap.INVALID_CREDENTIALS as e:
self.logger.logError(("!--- Invalid LDAP credentials {}" self.logger.logError(("!--- Invalid LDAP credentials {}"
" Please check {}" .format(e, self.CONFIGFILES[0]))) " Please check {}" .format(e, self.CONFIGFILES)))
l.unbind_s() l.unbind_s()
return None return None
except ldap.LDAPError as e: except ldap.LDAPError as e:
@ -118,19 +126,19 @@ class MoveToPublic(IMapiDAgentPlugin):
try: try:
result = l.search_s(self.config['search_base'], \ result = l.search_s(self.config['search_base'], \
ldap.SCOPE_SUBTREE, self.searchfilter(recipient), \ ldap.SCOPE_SUBTREE, self.searchfilter(recipient), \
[self.config['public_store_attribute']]) [self.config['public_folder_attribute']])
except ldap.LDAPError as e: except ldap.LDAPError as e:
self.logger.logError("!--- LDAPError {}".format(e)) self.logger.logError("!--- LDAPError {}".format(e))
l.unbind_s() l.unbind_s()
return result return result
def publicfolder(self, recipient): def publicfolder(self, recipient):
""" Check for ldap_public_store_attribute_token and return folder """ """ Check for ldap_public_folder_attribute_token and return folder """
destination_folder = [] destination_folder = []
result = self.searchquery(recipient) result = self.searchquery(recipient)
if result: if result:
tokenandfolder = (result[0][1] tokenandfolder = (result[0][1]
.get(self.config['public_store_attribute'])[0].decode('utf-8')) .get(self.config['public_folder_attribute'])[0].decode('utf-8'))
if tokenandfolder: if tokenandfolder:
destination_folder = tokenandfolder.split(':')[1] destination_folder = tokenandfolder.split(':')[1]
if destination_folder: if destination_folder:

View File

@ -25,9 +25,9 @@ class ldapstores():
'ldap_bind_passwd': None, 'ldap_bind_passwd': None,
'ldap_user_unique_attribute': "uid", 'ldap_user_unique_attribute': "uid",
'ldap_user_search_filter': "(kopanoAccount=1)", 'ldap_user_search_filter': "(kopanoAccount=1)",
# 'ldap_user_search_filter': "(&(kopanoAccount=1)(kopanoResourceType=publicStore:*))", # 'ldap_user_search_filter': "(&(kopanoAccount=1)(kopanoResourceType=publicFolder:*))",
'ldap_public_store_attribute': "kopanoResourceType", 'ldap_public_folder_attribute': "kopanoResourceType",
'ldap_public_store_attribute_token': "publicStore" 'ldap_public_folder_attribute_token': "publicFolder"
} }
def __init__(self, configfile = '/etc/kopano/ldap.cfg'): def __init__(self, configfile = '/etc/kopano/ldap.cfg'):
@ -58,7 +58,7 @@ class ldapstores():
ldap_result_id = l.search(self.config['search_base'], \ ldap_result_id = l.search(self.config['search_base'], \
ldap.SCOPE_SUBTREE, self.config['user_search_filter'], \ ldap.SCOPE_SUBTREE, self.config['user_search_filter'], \
[self.config['user_unique_attribute'], \ [self.config['user_unique_attribute'], \
self.config['public_store_attribute']]) self.config['public_folder_attribute']])
results = [] results = []
while 1: while 1:
result_type, result_data = l.result(ldap_result_id, 0) result_type, result_data = l.result(ldap_result_id, 0)
@ -77,11 +77,11 @@ class ldapstores():
public = [] public = []
for store in stores: for store in stores:
recipient = store[1].get(self.config['user_unique_attribute']) recipient = store[1].get(self.config['user_unique_attribute'])
tokenandfolder = store[1].get(self.config['ldap_public_store_attribute']) tokenandfolder = store[1].get(self.config['ldap_public_folder_attribute'])
if tokenandfolder: if tokenandfolder:
token = tokenandfolder.split(':')[0] token = tokenandfolder.split(':')[0]
destination_folder = tokenandfolder.split(':')[1] destination_folder = tokenandfolder.split(':')[1]
if (token == self.config['ldap_public_store_attribute_token']); if (token == self.config['ldap_public_folder_attribute_token']);
public[recipient] = destination_folder public[recipient] = destination_folder
return public return public