Switch from python3-ldap to ldap3 package
authorThierry Florac <thierry.florac@onf.fr>
Tue, 06 Jun 2017 18:11:52 +0200
changeset 20 68b5251b9687
parent 19 0e4b82b519c7
child 21 e431d2f1671f
Switch from python3-ldap to ldap3 package
src/pyams_ldap/interfaces/__init__.py
src/pyams_ldap/locales/fr/LC_MESSAGES/pyams_ldap.mo
src/pyams_ldap/locales/fr/LC_MESSAGES/pyams_ldap.po
src/pyams_ldap/locales/pyams_ldap.pot
src/pyams_ldap/plugin.py
src/pyams_ldap/zmi/plugin.py
src/pyams_ldap/zmi/templates/ldap-attributes.pt
--- a/src/pyams_ldap/interfaces/__init__.py	Tue Nov 15 14:49:16 2016 +0100
+++ b/src/pyams_ldap/interfaces/__init__.py	Tue Jun 06 18:11:52 2017 +0200
@@ -15,7 +15,7 @@
 
 # import standard library
 import re
-from ldap3 import SEARCH_SCOPE_BASE_OBJECT, SEARCH_SCOPE_SINGLE_LEVEL, SEARCH_SCOPE_WHOLE_SUBTREE
+from ldap3 import BASE, LEVEL, SUBTREE
 
 # import interfaces
 from pyams_security.interfaces import IAuthenticationPlugin, IDirectorySearchPlugin
@@ -32,9 +32,9 @@
 # Search scopes vocabulary
 #
 
-SEARCH_SCOPES = {SEARCH_SCOPE_BASE_OBJECT: _("Base object"),
-                 SEARCH_SCOPE_SINGLE_LEVEL: _("Single level"),
-                 SEARCH_SCOPE_WHOLE_SUBTREE: _("Whole subtree")}
+SEARCH_SCOPES = {BASE: _("Base object"),
+                 LEVEL: _("Single level"),
+                 SUBTREE: _("Whole subtree")}
 
 SEARCH_SCOPES_VOCABULARY = SimpleVocabulary([SimpleTerm(v, title=t) for v, t in SEARCH_SCOPES.items()])
 
@@ -119,7 +119,7 @@
 
     search_scope = Choice(title=_("Search scope"),
                           vocabulary=SEARCH_SCOPES_VOCABULARY,
-                          default=SEARCH_SCOPE_WHOLE_SUBTREE,
+                          default=SUBTREE,
                           required=True)
 
     login_attribute = TextLine(title=_("Login attribute"),
@@ -165,7 +165,7 @@
 
     groups_search_scope = Choice(title=_("Groups search scope"),
                                  vocabulary=SEARCH_SCOPES_VOCABULARY,
-                                 default=SEARCH_SCOPE_WHOLE_SUBTREE,
+                                 default=SUBTREE,
                                  required=False)
 
     group_prefix = TextLine(title=_("Group prefix"),
Binary file src/pyams_ldap/locales/fr/LC_MESSAGES/pyams_ldap.mo has changed
--- a/src/pyams_ldap/locales/fr/LC_MESSAGES/pyams_ldap.po	Tue Nov 15 14:49:16 2016 +0100
+++ b/src/pyams_ldap/locales/fr/LC_MESSAGES/pyams_ldap.po	Tue Jun 06 18:11:52 2017 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-28 09:16+0100\n"
+"POT-Creation-Date: 2016-10-13 10:59+0200\n"
 "PO-Revision-Date: 2015-02-28 09:17+0100\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -16,249 +16,353 @@
 "Generated-By: Lingua 3.10.dev0\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
-#: src/pyams_ldap/zmi/plugin.py:68
+#: src/pyams_ldap/zmi/plugin.py:71
 msgid "Add LDAP users folder..."
-msgstr "Ajouter un dossier LDAP..."
+msgstr "Ajouter un annuaire LDAP..."
 
-#: src/pyams_ldap/zmi/plugin.py:84
+#: src/pyams_ldap/zmi/plugin.py:91
 msgid "System security manager"
 msgstr "Gestionnaire de sécurité"
 
-#: src/pyams_ldap/zmi/plugin.py:85
+#: src/pyams_ldap/zmi/plugin.py:92
 msgid "Add LDAP users folder plug-in"
-msgstr "Ajout d'un dossier d'accès LDAP"
+msgstr "Ajout d'un annuaire LDAP"
 
-#: src/pyams_ldap/zmi/plugin.py:118 src/pyams_ldap/zmi/plugin.py:206
+#: src/pyams_ldap/zmi/plugin.py:125 src/pyams_ldap/zmi/plugin.py:225
 msgid "Connection"
 msgstr "Connexion"
 
-#: src/pyams_ldap/zmi/plugin.py:130 src/pyams_ldap/zmi/plugin.py:218
+#: src/pyams_ldap/zmi/plugin.py:137 src/pyams_ldap/zmi/plugin.py:237
 msgid "Users schema"
 msgstr "Schéma des utilisateurs"
 
-#: src/pyams_ldap/zmi/plugin.py:142 src/pyams_ldap/zmi/plugin.py:230
+#: src/pyams_ldap/zmi/plugin.py:150 src/pyams_ldap/zmi/plugin.py:250
 msgid "Groups schema"
 msgstr "Schéma des groupes"
 
-#: src/pyams_ldap/zmi/plugin.py:154 src/pyams_ldap/zmi/plugin.py:242
+#: src/pyams_ldap/zmi/plugin.py:166 src/pyams_ldap/zmi/plugin.py:266
 msgid "Search settings"
 msgstr "Recherches"
 
-#: src/pyams_ldap/zmi/plugin.py:171
+#: src/pyams_ldap/zmi/plugin.py:190
 msgid "Edit LDAP users folder plug-in properties"
 msgstr "Modification des propriétés d'un dossier d'accès LDAP"
 
-#: src/pyams_ldap/zmi/plugin.py:272
-msgid "Search users and groups"
-msgstr "Utilisateurs et groupes"
+#: src/pyams_ldap/zmi/plugin.py:294
+msgid "Security manager"
+msgstr "Gestionnaire de sécurité"
 
-#: src/pyams_ldap/zmi/plugin.py:281
+#: src/pyams_ldap/zmi/plugin.py:307
 msgid "Search results"
 msgstr "Résultats de la recherche"
 
-#: src/pyams_ldap/zmi/plugin.py:309
+#: src/pyams_ldap/zmi/plugin.py:337
 msgid "Common name"
 msgstr "Nom courant"
 
-#: src/pyams_ldap/zmi/plugin.py:319
+#: src/pyams_ldap/zmi/plugin.py:347
 msgid "E-mail"
 msgstr "Adresse email"
 
-#: src/pyams_ldap/zmi/plugin.py:338
+#: src/pyams_ldap/zmi/plugin.py:366
 #, python-format
 msgid "Display LDAP entry: {dn}"
 msgstr "Entrée LDAP : {dn}"
 
-#: src/pyams_ldap/interfaces/__init__.py:29
+#: src/pyams_ldap/interfaces/__init__.py:35
 msgid "Base object"
 msgstr "Objet de base"
 
-#: src/pyams_ldap/interfaces/__init__.py:30
+#: src/pyams_ldap/interfaces/__init__.py:36
 msgid "Single level"
 msgstr "Un niveau"
 
-#: src/pyams_ldap/interfaces/__init__.py:31
+#: src/pyams_ldap/interfaces/__init__.py:37
 msgid "Whole subtree"
 msgstr "Sous-arbre complet"
 
-#: src/pyams_ldap/interfaces/__init__.py:39
+#: src/pyams_ldap/interfaces/__init__.py:46
+msgid "Use group attribute to get members list"
+msgstr "Un attribut du groupe contient la liste de ses membres"
+
+#: src/pyams_ldap/interfaces/__init__.py:47
+msgid "Use member attribute to get groups list"
+msgstr "Un attribut des membres contient ses groupes d'appartenance"
+
+#: src/pyams_ldap/interfaces/__init__.py:57
+msgid "none (only use members own mail address"
+msgstr "aucun (n'utiliser que l'adresse des membres)"
+
+#: src/pyams_ldap/interfaces/__init__.py:58
+msgid "Use group internal attribute"
+msgstr "Utiliser un attribut du groupe"
+
+#: src/pyams_ldap/interfaces/__init__.py:59
+msgid "Use another group internal attribute"
+msgstr "Utiliser un attribut d'un autre groupe"
+
+#: src/pyams_ldap/interfaces/__init__.py:87
 msgid "LDAP server URI"
 msgstr "URI du serveur"
 
-#: src/pyams_ldap/interfaces/__init__.py:40
+#: src/pyams_ldap/interfaces/__init__.py:88
 msgid "Full URI (including protocol) of LDAP server"
 msgstr "URI complète (y compris le protocole) d'accès au serveur LDAP"
 
-#: src/pyams_ldap/interfaces/__init__.py:44
+#: src/pyams_ldap/interfaces/__init__.py:92
 msgid "Bind DN"
 msgstr "DN de connexion"
 
-#: src/pyams_ldap/interfaces/__init__.py:45
+#: src/pyams_ldap/interfaces/__init__.py:93
 msgid "DN used for LDAP bind; keep empty for anonymous"
-msgstr "DN utilisé pour la connexion LDAP ; laissez vide pour une connexion anonyme"
+msgstr ""
+"DN utilisé pour la connexion LDAP ; laissez vide pour une connexion anonyme"
 
-#: src/pyams_ldap/interfaces/__init__.py:48
+#: src/pyams_ldap/interfaces/__init__.py:96
 msgid "Bind password"
 msgstr "Mot de passe"
 
-#: src/pyams_ldap/interfaces/__init__.py:49
+#: src/pyams_ldap/interfaces/__init__.py:97
 msgid "Password used for LDAP bind"
 msgstr "Mot de passe utilisé pour la connexion LDAP"
 
-#: src/pyams_ldap/interfaces/__init__.py:52
+#: src/pyams_ldap/interfaces/__init__.py:100
 msgid "Use TLS?"
 msgstr "Utiliser TLS ?"
 
-#: src/pyams_ldap/interfaces/__init__.py:56
+#: src/pyams_ldap/interfaces/__init__.py:104
 msgid "Use connections pool?"
 msgstr "Pool de connexions ?"
 
-#: src/pyams_ldap/interfaces/__init__.py:60
+#: src/pyams_ldap/interfaces/__init__.py:108
 msgid "Pool size"
 msgstr "Taille du pool"
 
-#: src/pyams_ldap/interfaces/__init__.py:64
+#: src/pyams_ldap/interfaces/__init__.py:112
 msgid "Pool lifetime"
 msgstr "Durée de vie du pool"
 
-#: src/pyams_ldap/interfaces/__init__.py:65
+#: src/pyams_ldap/interfaces/__init__.py:113
 msgid "Duration, in seconds, of pool lifetime"
 msgstr "En secondes"
 
-#: src/pyams_ldap/interfaces/__init__.py:68
+#: src/pyams_ldap/interfaces/__init__.py:116
 msgid "Base DN"
 msgstr "DN de base"
 
-#: src/pyams_ldap/interfaces/__init__.py:69
+#: src/pyams_ldap/interfaces/__init__.py:117
 msgid "LDAP base DN"
 msgstr "DN de base pour la recherche des utilisateurs"
 
-#: src/pyams_ldap/interfaces/__init__.py:72
+#: src/pyams_ldap/interfaces/__init__.py:120
 msgid "Search scope"
 msgstr "Portée de la recherche"
 
-#: src/pyams_ldap/interfaces/__init__.py:77
+#: src/pyams_ldap/interfaces/__init__.py:125
 msgid "Login attribute"
 msgstr "Attribut de connexion"
 
-#: src/pyams_ldap/interfaces/__init__.py:78
+#: src/pyams_ldap/interfaces/__init__.py:126
 msgid "LDAP attribute used as user login"
 msgstr "Nom de l'attribut LDAP utilisé lors de la connexion"
 
-#: src/pyams_ldap/interfaces/__init__.py:82
+#: src/pyams_ldap/interfaces/__init__.py:130
 msgid "Login query"
 msgstr "Requête de connexion"
 
-#: src/pyams_ldap/interfaces/__init__.py:83
+#: src/pyams_ldap/interfaces/__init__.py:131
 msgid ""
 "Query template used to authenticate user (based on login attribute called "
 "'login')"
 msgstr ""
-"Modèle de la requête utilisée lors de la connexion d'un utilisateur ; "
-"la variable 'login' correspond à la saisie de l'utilisateur"
+"Modèle de la requête utilisée lors de la connexion d'un utilisateur ; la "
+"variable 'login' correspond à la saisie de l'utilisateur"
 
-#: src/pyams_ldap/interfaces/__init__.py:88
+#: src/pyams_ldap/interfaces/__init__.py:136
 msgid "UID attribute"
 msgstr "Attribut UID"
 
-#: src/pyams_ldap/interfaces/__init__.py:89
+#: src/pyams_ldap/interfaces/__init__.py:137
 msgid "LDAP attribute used as principal identifier"
 msgstr "Attribut LDAP unique utilisé pour l'identification d'un utilisateur"
 
-#: src/pyams_ldap/interfaces/__init__.py:93
+#: src/pyams_ldap/interfaces/__init__.py:141
 msgid "UID query"
 msgstr "Requête d'UID"
 
-#: src/pyams_ldap/interfaces/__init__.py:94
+#: src/pyams_ldap/interfaces/__init__.py:142
 msgid ""
 "Query template used to get principal information (based on UID attribute "
 "called 'login')"
 msgstr ""
-"Modèle de la requête utilisée pour rechercher les informations relatives à un "
-"utilisateur à partir de son UID (variable 'login')"
+"Modèle de la requête utilisée pour rechercher les informations relatives à "
+"un utilisateur à partir de son UID (variable 'login')"
 
-#: src/pyams_ldap/interfaces/__init__.py:99
+#: src/pyams_ldap/interfaces/__init__.py:147
 msgid "Title format"
 msgstr "Format du nom"
 
-#: src/pyams_ldap/interfaces/__init__.py:100
-#: src/pyams_ldap/interfaces/__init__.py:132
+#: src/pyams_ldap/interfaces/__init__.py:148
+#: src/pyams_ldap/interfaces/__init__.py:182
 msgid "Principal's title format string"
 msgstr "Chaîne de formatage du nom"
 
-#: src/pyams_ldap/interfaces/__init__.py:104
+#: src/pyams_ldap/interfaces/__init__.py:152
+#: src/pyams_ldap/interfaces/__init__.py:223
+msgid "Mail attribute"
+msgstr "Attribut 'adresse'"
+
+#: src/pyams_ldap/interfaces/__init__.py:153
+msgid "LDAP attribute storing mail address"
+msgstr "Nom de l'attribut LDAP contenant l'adresse de messagerie"
+
+#: src/pyams_ldap/interfaces/__init__.py:157
+#: src/pyams_ldap/interfaces/__init__.py:228
+msgid "Extra attributes"
+msgstr "Autres attributs"
+
+#: src/pyams_ldap/interfaces/__init__.py:158
+#: src/pyams_ldap/interfaces/__init__.py:229
+msgid "Comma separated list of additional attributes"
+msgstr "Liste d'attributs supplémentaires à extraire, séparés par des virgules"
+
+#: src/pyams_ldap/interfaces/__init__.py:161
 msgid "Groups base DN"
 msgstr "DN de base"
 
-#: src/pyams_ldap/interfaces/__init__.py:105
+#: src/pyams_ldap/interfaces/__init__.py:162
 msgid "Base DN used to search LDAP groups; keep empty to disable groups usage"
-msgstr "DN de base pour la recherche des groupes ; laissez le vide pour ne "
-"pas activer la gestion des groupes"
+msgstr ""
+"DN de base pour la recherche des groupes ; laissez le vide pour ne pas "
+"activer la gestion des groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:109
+#: src/pyams_ldap/interfaces/__init__.py:166
 msgid "Groups search scope"
 msgstr "Portée de la recherche"
 
-#: src/pyams_ldap/interfaces/__init__.py:114
-msgid "Groups query"
-msgstr "Recherche des groupes"
-
-#: src/pyams_ldap/interfaces/__init__.py:115
-msgid ""
-"Query template used to get principal groups (based on DN and UID attributes "
-"called 'dn' and 'login')"
-msgstr ""
-"Modèle de requête utilisée pour extraire la liste des groupes d'un utilisateur "
-"(à partir de ses attributes appelés 'dn' et 'uid')"
-
-#: src/pyams_ldap/interfaces/__init__.py:121
+#: src/pyams_ldap/interfaces/__init__.py:171
 msgid "Group prefix"
 msgstr "Préfixe des groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:122
+#: src/pyams_ldap/interfaces/__init__.py:172
 msgid "Prefix used to identify groups"
 msgstr "Préfixe utilisé pour identifier les groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:126
+#: src/pyams_ldap/interfaces/__init__.py:176
 msgid "Group UID attribute"
 msgstr "Attribut UID"
 
-#: src/pyams_ldap/interfaces/__init__.py:127
+#: src/pyams_ldap/interfaces/__init__.py:177
 msgid "LDAP attribute used as group identifier"
 msgstr "Attribut LDAP utilisé pour identifier les groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:131
+#: src/pyams_ldap/interfaces/__init__.py:181
 msgid "Group title format"
 msgstr "Format du nom"
 
-#: src/pyams_ldap/interfaces/__init__.py:136
+#: src/pyams_ldap/interfaces/__init__.py:186
+msgid "Members query mode"
+msgstr "Recherche des membres"
+
+#: src/pyams_ldap/interfaces/__init__.py:187
+msgid "Define how groups members are defined"
+msgstr "Indique la façon dont les membres d'un groupe sont définis"
+
+#: src/pyams_ldap/interfaces/__init__.py:191
+msgid "Groups query"
+msgstr "Recherche des groupes"
+
+#: src/pyams_ldap/interfaces/__init__.py:192
+msgid ""
+"When members are store inside a group attribute, this query template is used "
+"to get principal groups (based on DN and UID attributes called 'dn' and "
+"'login')"
+msgstr ""
+"Lorsque les membres d'un groupe sont définis via un attribut du groupe, ce modèle de "
+"requête est utilisé pour extraire la liste des groupes d'un "
+"utilisateur (à partir de ses attributes appelés 'dn' et 'uid')"
+
+#: src/pyams_ldap/interfaces/__init__.py:199
+msgid "Group members attribute"
+msgstr "Membres d'un groupe"
+
+#: src/pyams_ldap/interfaces/__init__.py:200
+msgid ""
+"When groups members are stored inside a group attribute, this is the "
+"attribute name"
+msgstr ""
+"Lorsque les membres d'un groupe sont définis via un attribut du groupe, ceci est le "
+"nom de l'attribut concerné"
+
+#: src/pyams_ldap/interfaces/__init__.py:205
+msgid "User groups attribute"
+msgstr "Groupes d'un membre"
+
+#: src/pyams_ldap/interfaces/__init__.py:206
+msgid ""
+"When user groups are stored inside a user attribute, this is the attribute "
+"name"
+msgstr ""
+"Lorsque les groupes d'un utilisateur sont définis par un attribut de "
+"l'utilisateur, ceci est le nom de l'attribut concerné"
+
+#: src/pyams_ldap/interfaces/__init__.py:211
+msgid "Group mail mode"
+msgstr "Messagerie de groupe"
+
+#: src/pyams_ldap/interfaces/__init__.py:212
+msgid "Define how an email can be sent to group members"
+msgstr "Déterminer la façon dont un message peut être envoyé à tous les membres d'un groupe"
+
+#: src/pyams_ldap/interfaces/__init__.py:217
+msgid "DN replace expression"
+msgstr "Remplacement du DN"
+
+#: src/pyams_ldap/interfaces/__init__.py:218
+msgid ""
+"In 'redirect' mail mode, specify source and target DN parts, separated by a "
+"pipe"
+msgstr ""
+"En mode 'redirection', indique les éléments du DN à remplacer, "
+"sous la forme 'DN source|DN destination'"
+
+#: src/pyams_ldap/interfaces/__init__.py:224
+msgid "In 'internal' mail mode, specify name of group mail attribute"
+msgstr ""
+"En mode 'direct', indique le nom de l'attribut contenant l'adresse de "
+"messagerie du groupe"
+
+#: src/pyams_ldap/interfaces/__init__.py:232
 msgid "Users select query"
 msgstr "Sélection des utilisateurs"
 
-#: src/pyams_ldap/interfaces/__init__.py:137
+#: src/pyams_ldap/interfaces/__init__.py:233
 msgid "Query template used to select users"
 msgstr "Modèle de la requête utilisée pour la sélection des utilisateurs"
 
-#: src/pyams_ldap/interfaces/__init__.py:141
+#: src/pyams_ldap/interfaces/__init__.py:237
 msgid "Users search query"
 msgstr "Recherche des utilisateurs"
 
-#: src/pyams_ldap/interfaces/__init__.py:142
+#: src/pyams_ldap/interfaces/__init__.py:238
 msgid "Query template used to search users"
 msgstr "Modèle de la requête utilisée pour la recherche des utilisateurs"
 
-#: src/pyams_ldap/interfaces/__init__.py:146
+#: src/pyams_ldap/interfaces/__init__.py:242
 msgid "Groups select query"
 msgstr "Sélection des groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:147
+#: src/pyams_ldap/interfaces/__init__.py:243
 msgid "Query template used to select groups"
 msgstr "Modèle de la requête utilisée pour la sélection des groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:151
+#: src/pyams_ldap/interfaces/__init__.py:247
 msgid "Groups search query"
 msgstr "Recherche des groupes"
 
-#: src/pyams_ldap/interfaces/__init__.py:152
+#: src/pyams_ldap/interfaces/__init__.py:248
 msgid "Query template used to search groups"
 msgstr "Modèle de la requête utilisée pour la recherche des groupes"
+
+#~ msgid "Search users and groups"
+#~ msgstr "Utilisateurs et groupes"
--- a/src/pyams_ldap/locales/pyams_ldap.pot	Tue Nov 15 14:49:16 2016 +0100
+++ b/src/pyams_ldap/locales/pyams_ldap.pot	Tue Jun 06 18:11:52 2017 +0200
@@ -1,12 +1,12 @@
 # 
 # SOME DESCRIPTIVE TITLE
 # This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-28 09:16+0100\n"
+"POT-Creation-Date: 2016-10-13 10:59+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -16,242 +16,332 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Lingua 3.10.dev0\n"
 
-#: ./src/pyams_ldap/zmi/plugin.py:68
+#: ./src/pyams_ldap/zmi/plugin.py:71
 msgid "Add LDAP users folder..."
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:84
+#: ./src/pyams_ldap/zmi/plugin.py:91
 msgid "System security manager"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:85
+#: ./src/pyams_ldap/zmi/plugin.py:92
 msgid "Add LDAP users folder plug-in"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:118 ./src/pyams_ldap/zmi/plugin.py:206
+#: ./src/pyams_ldap/zmi/plugin.py:125 ./src/pyams_ldap/zmi/plugin.py:225
 msgid "Connection"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:130 ./src/pyams_ldap/zmi/plugin.py:218
+#: ./src/pyams_ldap/zmi/plugin.py:137 ./src/pyams_ldap/zmi/plugin.py:237
 msgid "Users schema"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:142 ./src/pyams_ldap/zmi/plugin.py:230
+#: ./src/pyams_ldap/zmi/plugin.py:150 ./src/pyams_ldap/zmi/plugin.py:250
 msgid "Groups schema"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:154 ./src/pyams_ldap/zmi/plugin.py:242
+#: ./src/pyams_ldap/zmi/plugin.py:166 ./src/pyams_ldap/zmi/plugin.py:266
 msgid "Search settings"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:171
+#: ./src/pyams_ldap/zmi/plugin.py:190
 msgid "Edit LDAP users folder plug-in properties"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:272
-msgid "Search users and groups"
+#: ./src/pyams_ldap/zmi/plugin.py:294
+msgid "Security manager"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:281
+#: ./src/pyams_ldap/zmi/plugin.py:307
 msgid "Search results"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:309
+#: ./src/pyams_ldap/zmi/plugin.py:337
 msgid "Common name"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:319
+#: ./src/pyams_ldap/zmi/plugin.py:347
 msgid "E-mail"
 msgstr ""
 
-#: ./src/pyams_ldap/zmi/plugin.py:338
+#: ./src/pyams_ldap/zmi/plugin.py:366
 #, python-format
 msgid "Display LDAP entry: {dn}"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:29
+#: ./src/pyams_ldap/interfaces/__init__.py:35
 msgid "Base object"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:30
+#: ./src/pyams_ldap/interfaces/__init__.py:36
 msgid "Single level"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:31
+#: ./src/pyams_ldap/interfaces/__init__.py:37
 msgid "Whole subtree"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:39
+#: ./src/pyams_ldap/interfaces/__init__.py:46
+msgid "Use group attribute to get members list"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:47
+msgid "Use member attribute to get groups list"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:57
+msgid "none (only use members own mail address"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:58
+msgid "Use group internal attribute"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:59
+msgid "Use another group internal attribute"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:87
 msgid "LDAP server URI"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:40
+#: ./src/pyams_ldap/interfaces/__init__.py:88
 msgid "Full URI (including protocol) of LDAP server"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:44
+#: ./src/pyams_ldap/interfaces/__init__.py:92
 msgid "Bind DN"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:45
+#: ./src/pyams_ldap/interfaces/__init__.py:93
 msgid "DN used for LDAP bind; keep empty for anonymous"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:48
+#: ./src/pyams_ldap/interfaces/__init__.py:96
 msgid "Bind password"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:49
+#: ./src/pyams_ldap/interfaces/__init__.py:97
 msgid "Password used for LDAP bind"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:52
+#: ./src/pyams_ldap/interfaces/__init__.py:100
 msgid "Use TLS?"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:56
+#: ./src/pyams_ldap/interfaces/__init__.py:104
 msgid "Use connections pool?"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:60
+#: ./src/pyams_ldap/interfaces/__init__.py:108
 msgid "Pool size"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:64
+#: ./src/pyams_ldap/interfaces/__init__.py:112
 msgid "Pool lifetime"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:65
+#: ./src/pyams_ldap/interfaces/__init__.py:113
 msgid "Duration, in seconds, of pool lifetime"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:68
+#: ./src/pyams_ldap/interfaces/__init__.py:116
 msgid "Base DN"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:69
+#: ./src/pyams_ldap/interfaces/__init__.py:117
 msgid "LDAP base DN"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:72
+#: ./src/pyams_ldap/interfaces/__init__.py:120
 msgid "Search scope"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:77
+#: ./src/pyams_ldap/interfaces/__init__.py:125
 msgid "Login attribute"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:78
+#: ./src/pyams_ldap/interfaces/__init__.py:126
 msgid "LDAP attribute used as user login"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:82
+#: ./src/pyams_ldap/interfaces/__init__.py:130
 msgid "Login query"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:83
+#: ./src/pyams_ldap/interfaces/__init__.py:131
 msgid ""
 "Query template used to authenticate user (based on login attribute called "
 "'login')"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:88
+#: ./src/pyams_ldap/interfaces/__init__.py:136
 msgid "UID attribute"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:89
+#: ./src/pyams_ldap/interfaces/__init__.py:137
 msgid "LDAP attribute used as principal identifier"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:93
+#: ./src/pyams_ldap/interfaces/__init__.py:141
 msgid "UID query"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:94
+#: ./src/pyams_ldap/interfaces/__init__.py:142
 msgid ""
 "Query template used to get principal information (based on UID attribute "
 "called 'login')"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:99
+#: ./src/pyams_ldap/interfaces/__init__.py:147
 msgid "Title format"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:100
-#: ./src/pyams_ldap/interfaces/__init__.py:132
+#: ./src/pyams_ldap/interfaces/__init__.py:148
+#: ./src/pyams_ldap/interfaces/__init__.py:182
 msgid "Principal's title format string"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:104
+#: ./src/pyams_ldap/interfaces/__init__.py:152
+#: ./src/pyams_ldap/interfaces/__init__.py:223
+msgid "Mail attribute"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:153
+msgid "LDAP attribute storing mail address"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:157
+#: ./src/pyams_ldap/interfaces/__init__.py:228
+msgid "Extra attributes"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:158
+#: ./src/pyams_ldap/interfaces/__init__.py:229
+msgid "Comma separated list of additional attributes"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:161
 msgid "Groups base DN"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:105
+#: ./src/pyams_ldap/interfaces/__init__.py:162
 msgid "Base DN used to search LDAP groups; keep empty to disable groups usage"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:109
+#: ./src/pyams_ldap/interfaces/__init__.py:166
 msgid "Groups search scope"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:114
-msgid "Groups query"
-msgstr ""
-
-#: ./src/pyams_ldap/interfaces/__init__.py:115
-msgid ""
-"Query template used to get principal groups (based on DN and UID attributes "
-"called 'dn' and 'login')"
-msgstr ""
-
-#: ./src/pyams_ldap/interfaces/__init__.py:121
+#: ./src/pyams_ldap/interfaces/__init__.py:171
 msgid "Group prefix"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:122
+#: ./src/pyams_ldap/interfaces/__init__.py:172
 msgid "Prefix used to identify groups"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:126
+#: ./src/pyams_ldap/interfaces/__init__.py:176
 msgid "Group UID attribute"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:127
+#: ./src/pyams_ldap/interfaces/__init__.py:177
 msgid "LDAP attribute used as group identifier"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:131
+#: ./src/pyams_ldap/interfaces/__init__.py:181
 msgid "Group title format"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:136
+#: ./src/pyams_ldap/interfaces/__init__.py:186
+msgid "Members query mode"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:187
+msgid "Define how groups members are defined"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:191
+msgid "Groups query"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:192
+msgid ""
+"When members are store inside a group attribute, this query template is used "
+"to get principal groups (based on DN and UID attributes called 'dn' and "
+"'login')"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:199
+msgid "Group members attribute"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:200
+msgid ""
+"When groups members are stored inside a group attribute, this is the "
+"attribute name"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:205
+msgid "User groups attribute"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:206
+msgid ""
+"When user groups are stored inside a user attribute, this is the attribute "
+"name"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:211
+msgid "Group mail mode"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:212
+msgid "Define how an email can be sent to group members"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:217
+msgid "DN replace expression"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:218
+msgid ""
+"In 'redirect' mail mode, specify source and target DN parts, separated by a "
+"pipe"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:224
+msgid "In 'internal' mail mode, specify name of group mail attribute"
+msgstr ""
+
+#: ./src/pyams_ldap/interfaces/__init__.py:232
 msgid "Users select query"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:137
+#: ./src/pyams_ldap/interfaces/__init__.py:233
 msgid "Query template used to select users"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:141
+#: ./src/pyams_ldap/interfaces/__init__.py:237
 msgid "Users search query"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:142
+#: ./src/pyams_ldap/interfaces/__init__.py:238
 msgid "Query template used to search users"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:146
+#: ./src/pyams_ldap/interfaces/__init__.py:242
 msgid "Groups select query"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:147
+#: ./src/pyams_ldap/interfaces/__init__.py:243
 msgid "Query template used to select groups"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:151
+#: ./src/pyams_ldap/interfaces/__init__.py:247
 msgid "Groups search query"
 msgstr ""
 
-#: ./src/pyams_ldap/interfaces/__init__.py:152
+#: ./src/pyams_ldap/interfaces/__init__.py:248
 msgid "Query template used to search groups"
 msgstr ""
--- a/src/pyams_ldap/plugin.py	Tue Nov 15 14:49:16 2016 +0100
+++ b/src/pyams_ldap/plugin.py	Tue Jun 06 18:11:52 2017 +0200
@@ -54,12 +54,12 @@
         self.bind_dn = plugin.bind_dn
         self.password = plugin.bind_password
         if plugin.use_pool:
-            self.strategy = ldap3.STRATEGY_REUSABLE_THREADED
+            self.strategy = ldap3.REUSABLE
             self.pool_name = 'pyams_ldap:{prefix}'.format(prefix=plugin.prefix)
             self.pool_size = plugin.pool_size
             self.pool_lifetime = plugin.pool_lifetime
         else:
-            self.strategy = ldap3.STRATEGY_ASYNC_THREADED
+            self.strategy = ldap3.ASYNC
             self.pool_name = None
             self.pool_size = None
             self.pool_lifetime = None
@@ -68,7 +68,7 @@
         if user:
             conn = ldap3.Connection(self.server,
                                     user=user, password=password,
-                                    client_strategy=ldap3.STRATEGY_SYNC,
+                                    client_strategy=ldap3.SYNC,
                                     auto_bind=True, lazy=False, read_only=True)
         else:
             conn = ldap3.Connection(self.server,
@@ -151,7 +151,7 @@
             target_dn = group.dn.replace(source, target)
             conn = plugin.get_connection()
             attributes = FORMAT_ATTRIBUTES.findall(plugin.group_title_format) + [plugin.group_mail_attribute]
-            search = LDAPQuery(target_dn, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, attributes)
+            search = LDAPQuery(target_dn, '(objectClass=*)', ldap3.BASE, attributes)
             result = search.execute(conn)
             if not result or len(result) > 1:
                 raise StopIteration
@@ -281,7 +281,7 @@
         try:
             login_conn = self.get_connection(user=login_dn, password=password)
             login_conn.unbind()
-        except ldap3.LDAPException:
+        except ldap3.core.exceptions.LDAPBindError:
             logger.debug("LDAP authentication exception with login %r", login, exc_info=True)
             return None
         else:
@@ -311,7 +311,7 @@
             if self.group_extra_attributes:
                 attributes += self.group_extra_attributes.split(',')
             if self.group_uid_attribute == 'dn':
-                search = LDAPQuery(group_id, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, attributes)
+                search = LDAPQuery(group_id, '(objectClass=*)', ldap3.BASE, attributes)
             else:
                 search = LDAPQuery(self.base_dn, self.uid_query, self.search_scope, attributes)
             result = search.execute(conn, login=group_id)
@@ -334,7 +334,7 @@
             if self.user_extra_attributes:
                 attributes += self.user_extra_attributes.split(',')
             if self.uid_attribute == 'dn':
-                search = LDAPQuery(login, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, attributes)
+                search = LDAPQuery(login, '(objectClass=*)', ldap3.BASE, attributes)
             else:
                 search = LDAPQuery(self.base_dn, self.uid_query, self.search_scope, attributes)
             result = search.execute(conn, login=login)
@@ -375,7 +375,7 @@
             # a member defines it's groups
             conn = self.get_connection()
             attributes = [self.user_groups_attribute]
-            user_search = LDAPQuery(principal_dn, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, attributes)
+            user_search = LDAPQuery(principal_dn, '(objectClass=*)', ldap3.BASE, attributes)
             for user_dn, user_attrs in user_search.execute(conn):
                 if self.group_uid_attribute == 'dn':
                     for group_dn in user_attrs.get(self.user_groups_attribute, ()):
@@ -385,7 +385,7 @@
                 else:
                     attributes = [self.group_uid_attribute]
                     for group_dn in user_attrs.get(self.user_groups_attribute, ()):
-                        group_search = LDAPQuery(group_dn, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT,
+                        group_search = LDAPQuery(group_dn, '(objectClass=*)', ldap3.BASE,
                                                  attributes)
                         for group_search_dn, group_search_attrs in group_search.execute(conn):
                             yield '{prefix}:{group_prefix}:{attr}'.format(prefix=self.prefix,
@@ -415,10 +415,10 @@
             # group members are defined into group attribute
             attributes = [self.group_members_attribute]
             user_attributes = FORMAT_ATTRIBUTES.findall(self.title_format) + [self.mail_attribute]
-            search = LDAPQuery(group.dn, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, attributes)
+            search = LDAPQuery(group.dn, '(objectClass=*)', ldap3.BASE, attributes)
             for group_dn, attrs in search.execute(conn):
                 for user_dn in attrs.get(self.group_members_attribute):
-                    user_search = LDAPQuery(user_dn, '(objectClass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, user_attributes)
+                    user_search = LDAPQuery(user_dn, '(objectClass=*)', ldap3.BASE, user_attributes)
                     for user_search_dn, user_search_attrs in user_search.execute(conn):
                         if info:
                             yield PrincipalInfo(id='{prefix}:{dn}'.format(prefix=self.prefix,
@@ -499,6 +499,7 @@
             yield user_dn, user_attrs
         # groups search
         if self.groups_base_dn:
-            search = LDAPQuery(self.groups_base_dn, self.groups_search_query, self.groups_search_scope, ldap3.ALL_ATTRIBUTES)
+            search = LDAPQuery(self.groups_base_dn, self.groups_search_query, self.groups_search_scope,
+                               ldap3.ALL_ATTRIBUTES)
             for group_dn, group_attrs in search.execute(conn, query=query):
                 yield group_dn, group_attrs
--- a/src/pyams_ldap/zmi/plugin.py	Tue Nov 15 14:49:16 2016 +0100
+++ b/src/pyams_ldap/zmi/plugin.py	Tue Jun 06 18:11:52 2017 +0200
@@ -339,7 +339,8 @@
     """Base LDAP column"""
 
     def getValue(self, obj):
-        return ', '.join(obj[1].get(self.attrName, ()))
+        value = obj[1].get(self.attrName, ())
+        return ', '.join(value) if isinstance(value, (list, tuple)) else value
 
 
 @adapter_config(name='name', context=(ILDAPPlugin, IAdminLayer, LDAPPluginSearchResultsView),
@@ -396,7 +397,7 @@
         plugin = self.context
         conn = plugin.get_connection()
         dn = self.request.params.get('dn')
-        query = LDAPQuery(dn, '(objectclass=*)', ldap3.SEARCH_SCOPE_BASE_OBJECT, ldap3.ALL_ATTRIBUTES)
+        query = LDAPQuery(dn, '(objectclass=*)', ldap3.BASE, ldap3.ALL_ATTRIBUTES)
         result = query.execute(conn)
         if not result or len(result) > 1:
             return ()
--- a/src/pyams_ldap/zmi/templates/ldap-attributes.pt	Tue Nov 15 14:49:16 2016 +0100
+++ b/src/pyams_ldap/zmi/templates/ldap-attributes.pt	Tue Jun 06 18:11:52 2017 +0200
@@ -5,6 +5,7 @@
 	</tr>
 	<tr tal:repeat="attr view.attributes">
 		<td tal:content="attr[0]"></td>
-		<td tal:content="structure view.br.join(sorted(attr[1]))"></td>
+		<td tal:define="value attr[1]"
+			tal:content="structure view.br.join(sorted(value)) if isinstance(value, list) else value"></td>
 	</tr>
 </table>