Finalized social plug-ins, groups and roles integration
authorThierry Florac <thierry.florac@onf.fr>
Mon, 23 Feb 2015 17:55:05 +0100
changeset 2 94e76f8e9828
parent 1 5595823c66f1
child 3 1dca3f8a61e6
Finalized social plug-ins, groups and roles integration
src/pyams_security/__init__.py
src/pyams_security/include.py
src/pyams_security/interfaces/__init__.py
src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.mo
src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po
src/pyams_security/locales/pyams_security.pot
src/pyams_security/permission.py
src/pyams_security/plugin/group.py
src/pyams_security/plugin/social.py
src/pyams_security/plugin/userfolder.py
src/pyams_security/principal.py
src/pyams_security/property.py
src/pyams_security/resources/img/behance.ico
src/pyams_security/resources/img/bitbucket.ico
src/pyams_security/resources/img/bitly.ico
src/pyams_security/resources/img/cosm.ico
src/pyams_security/resources/img/deviantart.ico
src/pyams_security/resources/img/facebook.ico
src/pyams_security/resources/img/flickr.ico
src/pyams_security/resources/img/foursquare.ico
src/pyams_security/resources/img/github.ico
src/pyams_security/resources/img/google.ico
src/pyams_security/resources/img/linkedin.ico
src/pyams_security/resources/img/meetup.ico
src/pyams_security/resources/img/paypal.ico
src/pyams_security/resources/img/plurk.ico
src/pyams_security/resources/img/reddit.ico
src/pyams_security/resources/img/tumblr.ico
src/pyams_security/resources/img/twitter.ico
src/pyams_security/resources/img/ubuntuone.ico
src/pyams_security/resources/img/viadeo.ico
src/pyams_security/resources/img/vimeo.ico
src/pyams_security/resources/img/vk.ico
src/pyams_security/resources/img/windows_live.ico
src/pyams_security/resources/img/xero.ico
src/pyams_security/resources/img/xing.ico
src/pyams_security/resources/img/yahoo.ico
src/pyams_security/resources/img/yammer.ico
src/pyams_security/resources/img/yandex.ico
src/pyams_security/resources/js/security.js
src/pyams_security/resources/js/security.min.js
src/pyams_security/role.py
src/pyams_security/schema.py
src/pyams_security/security.py
src/pyams_security/utility.py
src/pyams_security/views/oauth.py
src/pyams_security/views/templates/social-login.pt
src/pyams_security/views/utility.py
src/pyams_security/widget/__init__.py
src/pyams_security/widget/interfaces.py
src/pyams_security/widget/templates/principal-display.pt
src/pyams_security/widget/templates/principal-input.pt
src/pyams_security/widget/templates/principals-set-display.pt
src/pyams_security/widget/templates/principals-set-input.pt
src/pyams_security/zmi/plugin/group.py
src/pyams_security/zmi/plugin/social.py
src/pyams_security/zmi/plugin/userfolder.py
src/pyams_security/zmi/security.py
src/pyams_security/zmi/utility.py
src/pyams_security/zmi/widget/__init__.py
src/pyams_security/zmi/widget/templates/ordered-list-input.pt
--- a/src/pyams_security/__init__.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/__init__.py	Mon Feb 23 17:55:05 2015 +0100
@@ -35,3 +35,19 @@
     """
     from .include import include_package
     include_package(config)
+
+    # register custom permissions
+    config.register_permission({'id': 'public',
+                                'title': _("View public contents")})
+    config.register_permission({'id': 'view',
+                                'title': _("View protected contents")})
+    config.register_permission({'id': 'manage',
+                                'title': _("Manage properties contents")})
+    config.register_permission({'id': 'system.view',
+                                'title': _("View management screens")})
+    config.register_permission({'id': 'system.manage',
+                                'title': _("Manage system")})
+
+    config.register_role({'id': 'system.Manager',
+                          'title': "System manager (role)",
+                          'permissions': {'public', 'view', 'manage', 'system.manage', 'system.view'}})
--- a/src/pyams_security/include.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/include.py	Mon Feb 23 17:55:05 2015 +0100
@@ -40,12 +40,6 @@
     # add custom routes
     config.add_route('login', '/login/{provider_name}')
 
-    # register custom permissions
-    config.register_permission({'id': 'system.manage',
-                                'title': "Manage system"})
-    config.register_permission({'id': 'system.view',
-                                'title': "View management screens"})
-
     # load registry components
     try:
         import pyams_zmi
--- a/src/pyams_security/interfaces/__init__.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/interfaces/__init__.py	Mon Feb 23 17:55:05 2015 +0100
@@ -9,6 +9,7 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+from zope.interface.interfaces import IObjectEvent, ObjectEvent
 
 __docformat__ = 'restructuredtext'
 
@@ -23,11 +24,11 @@
 from zope.location.interfaces import IContained
 
 # import packages
+from pyams_security.schema import PrincipalsSet
 from pyams_utils.schema import EncodedPassword
-from zope.configuration.fields import GlobalObject
 from zope.container.constraints import contains, containers
 from zope.interface import implementer, Interface, Attribute, invariant, Invalid
-from zope.schema import TextLine, Text, Int, Bool, List, Tuple, Set, Dict, Choice, Datetime
+from zope.schema import TextLine, Text, Int, Bool, List, Tuple, Set, Dict, Choice, Datetime, Object
 
 from pyams_security import _
 
@@ -184,7 +185,10 @@
         """Returns all principals matching given principal ID"""
 
     def find_principals(self, query):
-        """Find principals matching given query"""
+        """Find principals matching given query
+
+        Method may return an iterator
+        """
 
 
 class IDirectoryPlugin(IDirectoryInfo, IPlugin):
@@ -194,18 +198,89 @@
 class IDirectorySearchPlugin(IDirectoryPlugin, IContentSearch):
     """Principal directory plug-in supporting search"""
 
+    def get_search_results(self, data):
+        """Search principals matching given query data
+
+        This method is used in back-office search views so may reply even
+        when the plug-in is disabled.
+        Method may return an iterator on his own content objects
+        """
+
+
+#
+# Social users interfaces
+#
+
+class ISocialUsersFolderPlugin(IDirectorySearchPlugin):
+    """Social users folder interface"""
+
+    contains('pyams_security.interfaces.ISocialUser')
+
+
+class ISocialUser(IAttributeAnnotatable):
+    """Social user interface"""
+
+    containers(ISocialUsersFolderPlugin)
+
+    user_id = TextLine(title=_("Internal provider ID"))
+
+    provider_name = TextLine(title=_("OAuth provider name"))
+
+    username = TextLine(title=_("User name"),
+                        required=False)
+
+    name = TextLine(title=_("Name"))
+
+    first_name = TextLine(title=_('First name'),
+                          required=False)
+
+    last_name = TextLine(title=_('Last name'),
+                         required=False)
+
+    nickname = TextLine(title=_('Nickname'),
+                        required=False)
+
+    email = TextLine(title=_("E-mail address"),
+                     required=False)
+
+    timezone = TextLine(title=_('Timezone'),
+                        required=False)
+
+    country = TextLine(title=_('Country'),
+                       required=False)
+
+    city = TextLine(title=_('City'),
+                    required=False)
+
+    postal_code = TextLine(title=_("Postal code"),
+                           required=False)
+
+    locale = TextLine(title=_('Locale code'),
+                      required=False)
+
+    picture = TextLine(title=_('Picture URL'),
+                       required=False)
+
+    birth_date = Datetime(title=_('Birth date'),
+                          required=False)
+
+    registration_date = Datetime(title=_("Registration date"),
+                                 readonly=True)
+
+
+#
+# User local registration
+#
 
 class IUsersFolderPlugin(IAuthenticationPlugin, IDirectorySearchPlugin):
     """Local users folder interface"""
 
+    contains('pyams_security.interfaces.ILocalUser')
+
     def check_login(self, login):
         """Check for existence of given login"""
 
 
-#
-# User registration
-#
-
 def check_password(password):
     """Check validity of a given password"""
     nbmaj = 0
@@ -303,6 +378,8 @@
 class ILocalUser(IAttributeAnnotatable):
     """Local user interface"""
 
+    containers(IUsersFolderPlugin)
+
     login = TextLine(title=_("User login"),
                      required=True,
                      readonly=True)
@@ -379,19 +456,100 @@
 
 
 #
+# Principals groups
+#
+
+class IGroupsFolderPlugin(IDirectorySearchPlugin):
+    """Principals groups folder plug-in"""
+
+    contains('pyams_security.interfaces.ILocalGroup')
+
+    def check_group_id(self, group_id):
+        """Check for existence of given group ID"""
+
+
+class ILocalGroup(Interface):
+    """Local principals group interface"""
+
+    containers(IGroupsFolderPlugin)
+
+    group_id = TextLine(title=_("Group ID"),
+                        description=_("This ID should be unique between all groups"),
+                        required=True,
+                        readonly=True)
+
+    title = TextLine(title=_("Title"),
+                     description=_("Public label of this group"),
+                     required=True)
+
+    description = Text(title=_("Description"),
+                       required=False)
+
+    principals = PrincipalsSet(title=_("Group principals"),
+                               description=_("IDs of principals contained in this group"),
+                               required=False,
+                               default=set())
+
+
+class IPrincipalsGroupEvent(Interface):
+    """Principals group event interface"""
+
+    group = Attribute("Event source group")
+
+    principals = Set(title="List of principals IDs",
+                     value_type=TextLine())
+
+
+class PrincipalsGroupEvent(object):
+    """Principals group event"""
+
+    def __init__(self, group, principals):
+        self.group = group
+        self.principals = principals
+
+
+class IPrincipalsAddedToGroupEvent(IPrincipalsGroupEvent):
+    """Interface of event fired when principals were added to group"""
+
+
+@implementer(IPrincipalsAddedToGroupEvent)
+class PrincipalsAddedToGroupEvent(PrincipalsGroupEvent):
+    """Event fired when principals were added to group"""
+
+
+class IPrincipalsRemovedFromGroupEvent(IPrincipalsGroupEvent):
+    """Interface of event fired when principals were removed from group"""
+
+
+@implementer(IPrincipalsRemovedFromGroupEvent)
+class PrincipalsRemovedFromGroupEvent(PrincipalsGroupEvent):
+    """Event fired when principals were removed from group"""
+
+
+#
 # Security manager
 #
 
-class ISecurityManager(IContainer, IDirectoryInfo):
+class ISecurityManager(IContainer, IDirectoryInfo, IAttributeAnnotatable):
     """Authentication and principals management utility"""
 
     contains(IPlugin)
 
     enable_social_login = Bool(title=_("Enable social login?"),
                                description=_("Enable login via social OAuth plug-ins"),
-                               required=True,
+                               required=False,
                                default=False)
 
+    social_users_folder = Choice(title=_("Social users folder"),
+                                 description=_("Name of folder used to store social users properties"),
+                                 required=False,
+                                 vocabulary='PyAMS social users folders')
+
+    @invariant
+    def check_social_users_folder(self):
+        if self.enable_social_login and not self.social_users_folder:
+            raise Invalid(_("You can't activate social login without selecting a social users folder"))
+
     authomatic_secret = TextLine(title=_("Authomatic secret"),
                                  description=_("This secret phrase is used to encrypt Authomatic cookie"),
                                  default='this is not a secret',
@@ -401,9 +559,9 @@
                                   required=True,
                                   default=False)
 
-    open_registration = Bool(title=_("Open registration?"),
+    open_registration = Bool(title=_("Enable free registration?"),
                              description=_("If 'Yes', any use will be able to create a new user account"),
-                             required=True,
+                             required=False,
                              default=False)
 
     users_folder = Choice(title=_("Users folder"),
@@ -463,38 +621,171 @@
 
 
 #
-# Social login configuration
+# Social login providers configuration
 #
 
+class ISocialLoginProviderInfo(Interface):
+    """Social login provider info
+
+    This interface is used to adapt providers to
+    get minimum information like icon class, URLs
+    required to get consumer elements...
+    """
+
+    name = TextLine(title="Provider name")
+
+    provider = Attribute("Provider class")
+
+    icon_class = TextLine(title="Icon class",
+                          description="Fontawesome icon class",
+                          required=True)
+
+    icon_filename = TextLine(title="Color icon filename",
+                             required=True)
+
+    scope = List(title="User info scope",
+                 value_type=TextLine())
+
+
 class ISocialLoginConfiguration(Interface):
     """Social login configuration interface"""
 
-    configuration = Dict(title=_("Social login configuration"),
-                         key_type=TextLine(title=_("Provider name")),
-                         value_type=Dict(title=_("Provider configuration")))
+    contains('pyams_securiy.interfaces.ISocialLoginProviderConnection')
+
+    def get_oauth_configuration(self):
+        """Get Authomatic configuration"""
 
 
-class ISocialLoginProviderInfo(Interface):
+class ISocialLoginProviderConnection(Interface):
     """Social login provider info"""
 
-    provider_name = TextLine(title=_("Provider name"),
-                             required=True)
+    containers(ISocialLoginConfiguration)
+
+    provider_name = Choice(title=_("Provider name"),
+                           vocabulary='PyAMS OAuth providers',
+                           required=True)
 
     provider_id = Int(title=_("Provider ID"),
                       description=_("This value should be unique between all providers"),
                       required=True,
                       min=0)
 
-    klass = GlobalObject(title=_("Provider class"),
-                         required=True)
-
     consumer_key = TextLine(title=_("Provider consumer key"),
                             required=True)
 
     consumer_secret = TextLine(title=_("Provider secret"),
                                required=True)
 
-    scope = List(title=_("Provider scope"),
-                 required=True,
-                 value_type=TextLine(),
-                 default=['email'])
+    def get_configuration(self):
+        """Get provider configuration"""
+
+
+#
+# Protected objects interfaces
+#
+
+class IProtectedObject(IAttributeAnnotatable):
+    """Protected object interface
+
+    This is the only interface used by authorization policy.
+    So you are free to implement custom protection mechanisms.
+    """
+
+    inherit_parent_security = Bool(title=_("Inherit parent security?"),
+                                   description=_("Get access control entries (ACE) inherited "
+                                                 "from parent levels"),
+                                   required=True,
+                                   default=True)
+
+    everyone_permission = Choice(title=_("Public permission"),
+                                 description=_("This permission will be granted to all users"),
+                                 vocabulary='PyAMS permissions',
+                                 required=False)
+
+    authenticated_permission = Choice(title=_("Authenticated permission"),
+                                      description=_("This permission will be granted to authenticated users"),
+                                      vocabulary='PyAMS permissions',
+                                      required=False)
+
+    inherit_parent_roles = Bool(title=_("Inherit parent roles?"),
+                                description=_("Get roles granted on parent levels"),
+                                required=True,
+                                default=True)
+
+    def __acl__(self):
+        """Object ACL"""
+
+    def get_principals(self, role_id):
+        """Get ID of principals who were granted given role
+
+        May return an empty set when empty
+        """
+
+    def get_roles(self, principal_id):
+        """Get ID of roles granted to given principal
+
+        May return an empty set when empty
+        """
+
+    def get_roles_ids(self, principal_id):
+        """Get ID of roles granted to given principal"""
+
+    def get_permissions(self, principal_id):
+        """Get ID of permissions granted to given principal"""
+
+
+class IRoleProtectedObject(IProtectedObject):
+    """Roles protected object interface"""
+
+    def grant_role(self, role, principal_ids):
+        """Grant given role to ptincipals"""
+
+    def revoke_role(self, role, principal_ids):
+        """Revoke given role from principals"""
+
+
+class IDefaultProtectionPolicy(Interface):
+    """Marker interface for objects using default protection policy"""
+
+    __roles__ = Tuple(title="Content roles",
+                      description="List of roles handles by this object",
+                      value_type=PrincipalsSet(),
+                      required=True)
+
+    roles_interface = Attribute("Name of interface containing roles fields")
+
+
+class IRoleEvent(IObjectEvent):
+    """Base role event interface"""
+
+    role_id = Attribute("Modified role ID")
+
+    principal_id = Attribute("Modified principal ID")
+
+
+@implementer(IRoleEvent)
+class RoleEvent(ObjectEvent):
+    """Base role event"""
+
+    def __init__(self, object, role_id, principal_id):
+        super(RoleEvent, self).__init__(object)
+        self.role_id = role_id
+        self.principal_id = principal_id
+
+
+class IGrantedRoleEvent(IRoleEvent):
+    """Granted role event interface"""
+
+
+@implementer(IGrantedRoleEvent)
+class GrantedRoleEvent(RoleEvent):
+    """Granted role event"""
+
+
+class IRevokedRoleEvent(IRoleEvent):
+    """Revoked role event interface"""
+
+
+@implementer(IRevokedRoleEvent)
+class RevokedRoleEvent(RoleEvent):
+    """Revoked role interface"""
Binary file src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.mo has changed
--- a/src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po	Mon Feb 23 17:55:05 2015 +0100
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-19 00:29+0100\n"
+"POT-Creation-Date: 2015-02-23 17:19+0100\n"
 "PO-Revision-Date: 2015-02-18 22:19+0100\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -16,44 +16,311 @@
 "Generated-By: Lingua 3.8\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
-#: src/pyams_security/principal.py:49
+#: src/pyams_security/__init__.py:41
+msgid "View public contents"
+msgstr "Voir les contenus publics"
+
+#: src/pyams_security/__init__.py:43
+msgid "View protected contents"
+msgstr "Voir les contenus protégés"
+
+#: src/pyams_security/__init__.py:45
+msgid "Manage properties contents"
+msgstr "Gérer les propriétés des contenus"
+
+#: src/pyams_security/__init__.py:47
+msgid "View management screens"
+msgstr "Voir les propriétés du système"
+
+#: src/pyams_security/__init__.py:49
+msgid "Manage system"
+msgstr "Gérer les propriétés du système"
+
+#: src/pyams_security/principal.py:52
 msgid "Not logged in"
 msgstr "Non connecté"
 
-#: src/pyams_security/zmi/utility.py:73
+#: src/pyams_security/widget/templates/principals-set-input.pt:3
+#: src/pyams_security/widget/templates/principal-input.pt:3
+msgid "Clear selected values"
+msgstr "Effacer les valeurs sélectionnées"
+
+#: src/pyams_security/zmi/utility.py:77
 msgid "Security"
 msgstr "Sécurité"
 
-#: src/pyams_security/zmi/utility.py:82
+#: src/pyams_security/zmi/utility.py:86
 msgid "Authentication and users directory plug-ins"
 msgstr "Modules d'authentification et dossiers utilisateurs"
 
-#: src/pyams_security/zmi/utility.py:127
+#: src/pyams_security/zmi/utility.py:108
+msgid "See plug-in contents"
+msgstr "Voir le contenu du module"
+
+#: src/pyams_security/zmi/utility.py:123
+#: src/pyams_security/zmi/plugin/social.py:186
+#: src/pyams_security/zmi/plugin/social.py:320
+#: src/pyams_security/zmi/plugin/userfolder.py:184
+#: src/pyams_security/zmi/plugin/group.py:151
+#: src/pyams_security/interfaces/__init__.py:232
+msgid "Name"
+msgstr "Nom"
+
+#: src/pyams_security/zmi/utility.py:134
+msgid "Delete plug-in"
+msgstr "Supprimer ce module"
+
+#: src/pyams_security/zmi/utility.py:175
+#: src/pyams_security/zmi/plugin/social.py:368
+#: src/pyams_security/zmi/plugin/social.py:386
+#: src/pyams_security/zmi/plugin/social.py:441
 msgid "Security manager"
 msgstr "Gestionnaire de sécurité"
 
-#: src/pyams_security/zmi/utility.py:128
+#: src/pyams_security/zmi/utility.py:176
 msgid "Security manager plug-ins"
 msgstr "Modules du gestionnaire de sécurité"
 
-#: src/pyams_security/zmi/utility.py:139
+#: src/pyams_security/zmi/utility.py:187
 msgid "Add..."
 msgstr "Ajouter..."
 
-#: src/pyams_security/zmi/utility.py:147
+#: src/pyams_security/zmi/utility.py:195
 msgid "Properties..."
 msgstr "Propriétés..."
 
-#: src/pyams_security/zmi/utility.py:160
+#: src/pyams_security/zmi/utility.py:208
+#: src/pyams_security/zmi/plugin/social.py:79
+#: src/pyams_security/zmi/plugin/userfolder.py:77
+#: src/pyams_security/zmi/plugin/group.py:73
 #: src/pyams_security/zmi/plugin/admin.py:61
-#: src/pyams_security/zmi/plugin/userfolder.py:74
 msgid "System security manager"
 msgstr "Gestionnaire de sécurité"
 
-#: src/pyams_security/zmi/utility.py:161
+#: src/pyams_security/zmi/utility.py:209
 msgid "Security manager properties"
 msgstr "Propriétés du gestionnaire de sécurité"
 
+#: src/pyams_security/zmi/utility.py:246
+msgid "Plug-ins"
+msgstr "Modules"
+
+#: src/pyams_security/zmi/utility.py:231
+#: src/pyams_security/interfaces/__init__.py:538
+msgid "Enable social login?"
+msgstr "Activer les réseaux sociaux ?"
+
+#: src/pyams_security/zmi/utility.py:238
+#: src/pyams_security/interfaces/__init__.py:562
+msgid "Enable free registration?"
+msgstr "Activer l'inscription libre ?"
+
+#: src/pyams_security/zmi/utility.py:268
+msgid "No provided plugin_name argument!"
+msgstr "Argument 'plugin_name' non fourni !"
+
+#: src/pyams_security/zmi/utility.py:272
+msgid "Given plug-in name doesn't exist!"
+msgstr "Le module indiqué n'existe pas !"
+
+#: src/pyams_security/zmi/security.py:41
+msgid "Access rules..."
+msgstr "Règles d'accès..."
+
+#: src/pyams_security/zmi/security.py:56
+msgid "Edit local roles"
+msgstr "Gestion des règles d'accès"
+
+#: src/pyams_security/zmi/security.py:72 src/pyams_security/zmi/security.py:73
+msgid "(inherit from parent)"
+msgstr "(héritée du parent)"
+
+#: src/pyams_security/zmi/plugin/social.py:68
+msgid "Add social users folder..."
+msgstr "Ajouter un dossier pour les réseaux sociaux..."
+
+#: src/pyams_security/zmi/plugin/social.py:80
+msgid "Add social users folder plug-in"
+msgstr "Ajout d'un dossier pour les réseaux sociaux"
+
+#: src/pyams_security/zmi/plugin/social.py:113
+msgid "Edit social users folder plug-in properties"
+msgstr "Modification d'un dossier pour les réseaux sociaux"
+
+#: src/pyams_security/zmi/plugin/social.py:154
+#: src/pyams_security/zmi/plugin/userfolder.py:152
+msgid "Search users"
+msgstr "Rechercher des utilisateurs"
+
+#: src/pyams_security/zmi/plugin/social.py:163
+#: src/pyams_security/zmi/plugin/userfolder.py:161
+msgid "Search results"
+msgstr "Résultats de la recherche"
+
+#: src/pyams_security/zmi/plugin/social.py:176
+msgid "User ID"
+msgstr "ID utilisateur"
+
+#: src/pyams_security/zmi/plugin/social.py:196
+#: src/pyams_security/zmi/plugin/userfolder.py:194
+#: src/pyams_security/interfaces/__init__.py:243
+#: src/pyams_security/interfaces/__init__.py:319
+msgid "E-mail address"
+msgstr "Adresse de messagerie"
+
+#: src/pyams_security/zmi/plugin/social.py:206
+#: src/pyams_security/interfaces/__init__.py:227
+msgid "OAuth provider name"
+msgstr "Nom du fournisseur"
+
+#: src/pyams_security/zmi/plugin/social.py:216
+#: src/pyams_security/zmi/plugin/userfolder.py:204
+#: src/pyams_security/interfaces/__init__.py:267
+msgid "Registration date"
+msgstr "Date d'enregistrement"
+
+#: src/pyams_security/zmi/plugin/social.py:239
+#: src/pyams_security/zmi/plugin/userfolder.py:324
+msgid "Edit user properties"
+msgstr "Modification des propriétés d'un utilisateur"
+
+#: src/pyams_security/zmi/plugin/social.py:271
+msgid "Social networks login"
+msgstr "Connexions aux réseaux sociaux"
+
+#: src/pyams_security/zmi/plugin/social.py:279
+msgid "Configured social networks login providers"
+msgstr "Connecteurs d'accès aux réseaux sociaux"
+
+#: src/pyams_security/zmi/plugin/social.py:310
+msgid "ID"
+msgstr "ID"
+
+#: src/pyams_security/zmi/plugin/social.py:331
+msgid "Delete provider"
+msgstr "Supprimer ce connecteur"
+
+#: src/pyams_security/zmi/plugin/social.py:369
+msgid "Social networks login providers"
+msgstr "Accès aux réseaux sociaux"
+
+#: src/pyams_security/zmi/plugin/social.py:377
+msgid "Add provider"
+msgstr "Ajouter un connecteur"
+
+#: src/pyams_security/zmi/plugin/social.py:387
+msgid "Add new social login provider"
+msgstr "Ajout d'un nouveau connecteur"
+
+#: src/pyams_security/zmi/plugin/social.py:442
+msgid "Edit social login provider properties"
+msgstr "Modification d'un connecteur"
+
+#: src/pyams_security/zmi/plugin/social.py:417
+msgid "This provider is already defined!"
+msgstr "Un connecteur pour ce réseau social est déjà défini !"
+
+#: src/pyams_security/zmi/plugin/social.py:433
+msgid "Social provider was created successfully"
+msgstr "Le connecteur a été créé avec succès."
+
+#: src/pyams_security/zmi/plugin/social.py:420
+msgid "This provider ID is already used!"
+msgstr "L'ID de ce connecteur est déjà utilisé !"
+
+#: src/pyams_security/zmi/plugin/social.py:473
+msgid "No provided provider_name argument!"
+msgstr "Argument 'provider_name' non fourni !"
+
+#: src/pyams_security/zmi/plugin/social.py:477
+msgid "Given provider name doesn't exist!"
+msgstr "Le connecteur indiqué n'existe pas !"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:66
+msgid "Add local users folder..."
+msgstr "Ajouter un dossier d'utilisateurs locaux..."
+
+#: src/pyams_security/zmi/plugin/userfolder.py:78
+msgid "Add local users folder plug-in"
+msgstr "Ajout d'un dossier d'utilisateurs locaux"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:111
+msgid "Edit local users folder plug-in properties"
+msgstr "Modification d'un dossier d'utilisateurs locaux"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:174
+#: src/pyams_security/views/login.py:56
+msgid "Login"
+msgstr "Code utilisateur"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:220
+#: src/pyams_security/interfaces/__init__.py:441
+#: src/pyams_security/interfaces/__init__.py:444
+msgid "Activation date"
+msgstr "Date d'activation"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:240
+msgid "Add user"
+msgstr "Ajouter un utilisateur"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:253
+msgid "Add new local user"
+msgstr "Ajout d'un utilisateur local"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:302
+#: src/pyams_security/views/userfolder.py:123
+msgid "Specified login can't be used!"
+msgstr "Le code utilisateur indiqué ne peut pas être utilisé !"
+
+#: src/pyams_security/zmi/plugin/userfolder.py:313
+msgid "User was created successfully"
+msgstr "L'utilisateur a été créé avec succès."
+
+#: src/pyams_security/zmi/plugin/group.py:62
+msgid "Add local groups folder..."
+msgstr "Ajouter un dossier de groupes locaux..."
+
+#: src/pyams_security/zmi/plugin/group.py:74
+msgid "Add local groups folder plug-in"
+msgstr "Ajout d'un dossier de groupes locaux"
+
+#: src/pyams_security/zmi/plugin/group.py:107
+msgid "Edit local groups folder plug-in properties"
+msgstr "Modification d'un dossier de groupes locaux"
+
+#: src/pyams_security/zmi/plugin/group.py:133
+msgid "Local groups"
+msgstr "Groupes locaux"
+
+#: src/pyams_security/zmi/plugin/group.py:161
+#: src/pyams_security/interfaces/__init__.py:485
+msgid "Description"
+msgstr "Description"
+
+#: src/pyams_security/zmi/plugin/group.py:191
+msgid "Groups list"
+msgstr "Liste des groupes"
+
+#: src/pyams_security/zmi/plugin/group.py:204
+msgid "Add group"
+msgstr "Ajouter un groupe"
+
+#: src/pyams_security/zmi/plugin/group.py:217
+msgid "Add new local group"
+msgstr "Ajout d'un groupe local"
+
+#: src/pyams_security/zmi/plugin/group.py:273
+msgid "Edit group properties"
+msgstr "Modification des propriétés d'un groupe"
+
+#: src/pyams_security/zmi/plugin/group.py:250
+msgid "Specified group ID can't be used!"
+msgstr "L'ID indiqué pour ce groupe ne peut pas être utilisé !"
+
+#: src/pyams_security/zmi/plugin/group.py:262
+msgid "Group was created successfully"
+msgstr "Le groupe a été créé avec succès."
+
 #: src/pyams_security/zmi/plugin/admin.py:50
 msgid "Add admin authentication..."
 msgstr "Ajouter un compte système..."
@@ -78,348 +345,79 @@
 "Avant de désactiver ce compte, veuillez vous assurer que vous disposer d'un "
 "autre compte d'administration correctement configuré !"
 
-#: src/pyams_security/zmi/plugin/userfolder.py:63
-msgid "Add local users folder..."
-msgstr "Ajouter un dossier d'utilisateurs locaux..."
-
-#: src/pyams_security/zmi/plugin/userfolder.py:75
-msgid "Add local users folder plug-in"
-msgstr "Ajout d'un dossier d'utilisateurs locaux"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:108
-msgid "Edit local users folder plug-in properties"
-msgstr "Modification d'un dossier d'utilisateurs locaux"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:158
-msgid "Search users"
-msgstr "Rechercher des utilisateurs"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:167
-msgid "Search results"
-msgstr "Résultats de la recherche"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:180
-#: src/pyams_security/views/login.py:56
-msgid "Login"
-msgstr "Code utilisateur"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:194
-msgid "Name"
-msgstr "Nom"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:208
-#: src/pyams_security/interfaces/__init__.py:244
-msgid "E-mail address"
-msgstr "Adresse de messagerie"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:222
-msgid "Registration date"
-msgstr "Date d'enregistrement"
+#: src/pyams_security/views/userfolder.py:53
+msgid "Register new account"
+msgstr "Créer un nouveau compte"
 
-#: src/pyams_security/zmi/plugin/userfolder.py:242
-#: src/pyams_security/interfaces/__init__.py:364
-#: src/pyams_security/interfaces/__init__.py:367
-msgid "Activation date"
-msgstr "Date d'activation"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:262
-msgid "Add user"
-msgstr "Ajouter un utilisateur"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:275
-msgid "Add new local user"
-msgstr "Ajout d'un utilisateur local"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:346
-msgid "Edit user properties"
-msgstr "Modification des propriétés d'un utilisateur"
+#: src/pyams_security/views/userfolder.py:73
+msgid "User registration"
+msgstr "Inscription"
 
-#: src/pyams_security/zmi/plugin/userfolder.py:324
-#: src/pyams_security/views/userfolder.py:121
-msgid "Specified login can't be used!"
-msgstr "Le code utilisateur indiqué ne peut pas être utilisé !"
-
-#: src/pyams_security/zmi/plugin/userfolder.py:335
-msgid "User was created successfully"
-msgstr "L'utilisateur a été créé avec succès."
-
-#: src/pyams_security/interfaces/__init__.py:130
-msgid "Plug-in prefix"
-msgstr "Préfixe du module"
-
-#: src/pyams_security/interfaces/__init__.py:131
-msgid ""
-"This prefix is mainly used by authentication plug-ins to mark principals"
-msgstr ""
-"Ce préfixe est utilisé par les modules d'authentification pour identifier "
-"les utilisateurs"
-
-#: src/pyams_security/interfaces/__init__.py:133
-msgid "Plug-in title"
-msgstr "Libellé du module"
+#: src/pyams_security/views/userfolder.py:74
+msgid "Please enter registration info"
+msgstr "Veuillez indiquer les paramètres de votre compte"
 
-#: src/pyams_security/interfaces/__init__.py:136
-msgid "Enabled plug-in?"
-msgstr "Module actif ?"
-
-#: src/pyams_security/interfaces/__init__.py:137
-msgid "You can choose to disable any plug-in..."
-msgstr ""
-"Un module inactif ne peut plus être utilisé pour authentifier ou rechercher "
-"les utilisateurs..."
-
-#: src/pyams_security/interfaces/__init__.py:172
-msgid "Admin. login"
-msgstr "Code utilisateur"
-
-#: src/pyams_security/interfaces/__init__.py:174
-msgid "Admin. password"
-msgstr "Mot de passe"
+#: src/pyams_security/views/userfolder.py:157
+msgid "User registration confirmation"
+msgstr "Confirmation d'inscription"
 
-#: src/pyams_security/interfaces/__init__.py:225
-msgid ""
-"Your password must contain at least three of these kinds of characters: "
-"lowercase letters, uppercase letters, numbers and special characters"
-msgstr ""
-"Votre mot de passe doit contenir au moins trois de ces types de caractères : "
-"minuscules, majuscules, chiffres et autres caractères"
-
-#: src/pyams_security/interfaces/__init__.py:235
-#: src/pyams_security/interfaces/__init__.py:286
-#: src/pyams_security/interfaces/__init__.py:306
-msgid "User login"
-msgstr "Code utilisateur"
-
-#: src/pyams_security/interfaces/__init__.py:236
-msgid ""
-"If you don't provide a custom login, your login will be your email address..."
-msgstr ""
-"Si vous n'indiquez pas de code utilisateur, vous pourrez utiliser votre "
-"adresse de messagerie pour vous connecter..."
+#: src/pyams_security/views/userfolder.py:158
+msgid "Please confirm your registration info"
+msgstr "Veuillez confirmer les paramètres de votre compte"
 
-#: src/pyams_security/interfaces/__init__.py:245
-msgid ""
-"An email will be sent to this address to validate account activation; it "
-"will be used as your future user login"
-msgstr ""
-"Un message sera envoyé à cette adresse pour la valider et pour vous "
-"permettre d'activer votre compte ; il pourra être utilisé compte identifiant "
-"de connexion si vous n'avez pas indiqué de code utilisateur"
-
-#: src/pyams_security/interfaces/__init__.py:254
-#: src/pyams_security/interfaces/__init__.py:323
-msgid "First name"
-msgstr "Prénom"
-
-#: src/pyams_security/interfaces/__init__.py:257
-#: src/pyams_security/interfaces/__init__.py:326
-msgid "Last name"
-msgstr "Nom"
+#: src/pyams_security/views/userfolder.py:65
+#: src/pyams_security/views/login.py:140
+msgid "Cancel"
+msgstr "Annuler"
 
-#: src/pyams_security/interfaces/__init__.py:260
-#: src/pyams_security/interfaces/__init__.py:331
-msgid "Company name"
-msgstr "Société"
-
-#: src/pyams_security/interfaces/__init__.py:263
-#: src/pyams_security/interfaces/__init__.py:289
-#: src/pyams_security/interfaces/__init__.py:339
-#: src/pyams_security/views/login.py:57
-msgid "Password"
-msgstr "Mot de passe"
-
-#: src/pyams_security/interfaces/__init__.py:264
-msgid ""
-"Password must be at least 8 characters long, and contain at least three kins "
-"of characters between lowercase letters, uppercase letters, numbers and "
-"special characters"
-msgstr ""
-"Le mot de passe doit être composé d'au moins huit caractères, et contenir au "
-"moins trois types de caractères parmi les lettres minuscules, les "
-"majuscules, les chiffres et les caractères spéciaux"
-
-#: src/pyams_security/interfaces/__init__.py:270
-#: src/pyams_security/interfaces/__init__.py:293
-msgid "Confirmed password"
-msgstr "Confirmation du mot de passe"
+#: src/pyams_security/views/userfolder.py:66
+msgid "Register"
+msgstr "Inscrire ce compte"
 
-#: src/pyams_security/interfaces/__init__.py:283
-#: src/pyams_security/interfaces/__init__.py:358
-msgid "Activation hash"
-msgstr "Clé d'activation"
-
-#: src/pyams_security/interfaces/__init__.py:315
-msgid "User email address"
-msgstr "Adresse de messagerie"
-
-#: src/pyams_security/interfaces/__init__.py:334
-msgid "Password manager name"
-msgstr "Gestionnaire de mot de passe"
-
-#: src/pyams_security/interfaces/__init__.py:343
-msgid "Wait confirmation?"
-msgstr "Attendre la confirmation ?"
-
-#: src/pyams_security/interfaces/__init__.py:344
-msgid ""
-"If 'no', user will be activated immediately without waiting email "
-"confirmation"
-msgstr ""
-"Si 'non', ce compte utilisateur sera activé immédiatement sans attendre le "
-"message de confirmation"
-
-#: src/pyams_security/interfaces/__init__.py:349
-msgid "Self-registered profile?"
-msgstr "Profil auto-enregistré ?"
-
-#: src/pyams_security/interfaces/__init__.py:354
-msgid "Activation secret key"
-msgstr "Clé secrète"
-
-#: src/pyams_security/interfaces/__init__.py:355
-msgid "This private secret is used to create and check activation hash"
-msgstr ""
-"Cette clé secrète est utilisé pour créer et vérifier la clé d'activation"
+#: src/pyams_security/views/userfolder.py:149
+msgid "Finalize registration"
+msgstr "Terminer mon inscription"
 
-#: src/pyams_security/interfaces/__init__.py:359
-msgid ""
-"This hash is provided into activation message URL. Activation hash is "
-"missing for local users which were registered without waiting their "
-"confirmation."
+#: src/pyams_security/views/userfolder.py:121
+msgid "Can't create user profile. Please contact system administrator."
 msgstr ""
-"Cette clé est fournie dans le message de confirmation de l'inscription. "
-"Cette clé d'activation n'est pas définie pour les utilisateurs pour lesquels "
-"l'attente de confirmation n'a pas été demandée."
-
-#: src/pyams_security/interfaces/__init__.py:390
-msgid "Enable social login?"
-msgstr "Activer les réseaux sociaux ?"
-
-#: src/pyams_security/interfaces/__init__.py:391
-msgid "Enable login via social OAuth plug-ins"
-msgstr ""
-"Autoriser la connexion à partir des réseaux sociaux via les modules OAuth"
-
-#: src/pyams_security/interfaces/__init__.py:395
-msgid "Authomatic secret"
-msgstr "Clé OAuth"
+"Impossible de créer votre compte utilisateur. Veuillez contacter "
+"l'administrateur."
 
-#: src/pyams_security/interfaces/__init__.py:396
-msgid "This secret phrase is used to encrypt Authomatic cookie"
+#: src/pyams_security/views/userfolder.py:195
+msgid "Can't check user profile. Please contact system administrator."
 msgstr ""
-"Cette phrase secrète est utilisée pour crypter le cookie d'authentification"
-
-#: src/pyams_security/interfaces/__init__.py:400
-msgid "Use social popup?"
-msgstr "Connection via une popup ?"
-
-#: src/pyams_security/interfaces/__init__.py:404
-msgid "Open registration?"
-msgstr "Inscription ouverte ?"
-
-#: src/pyams_security/interfaces/__init__.py:405
-msgid "If 'Yes', any use will be able to create a new user account"
-msgstr "Si 'oui', toute personne sera à même de se créer un compte utilisateur"
-
-#: src/pyams_security/interfaces/__init__.py:409
-msgid "Users folder"
-msgstr "Dossier d'utilisateurs"
-
-#: src/pyams_security/interfaces/__init__.py:410
-msgid "Name of users folder used to store registered principals"
-msgstr "Nom du dossier dans lequel seront créés les utilisateurs enregistrés"
+"Impossible de vérifier votre compte utilisateur. Veuillez contacter "
+"l'administrateur."
 
-#: src/pyams_security/interfaces/__init__.py:419
-msgid "Credentials plug-ins"
-msgstr "Modules d'identification"
-
-#: src/pyams_security/interfaces/__init__.py:420
-msgid "These plug-ins can be used to extract request credentials"
-msgstr ""
-"Ces modules peuvent être utilisés pour extraire l'identitité des utilisateurs"
-
-#: src/pyams_security/interfaces/__init__.py:425
-msgid "Authentication plug-ins"
-msgstr "Modules d'authentification"
-
-#: src/pyams_security/interfaces/__init__.py:426
-msgid ""
-"The plug-ins can be used to check extracted credentials against a local or "
-"remote users database"
-msgstr ""
-"Ces modules sont utilisés pour vérifier l'identité des utilisateurs vis à "
-"vis d'une base d'utilisateurs locale ou distante"
-
-#: src/pyams_security/interfaces/__init__.py:431
-msgid "Directory plug-ins"
-msgstr "Modules d'annuaires"
-
-#: src/pyams_security/interfaces/__init__.py:432
-msgid "The plug-in can be used to extract principals information"
-msgstr ""
-"Ces modules peuvent être utilisés pour extraire les propriétés des "
-"utilisateurs"
-
-#: src/pyams_security/interfaces/__init__.py:472
-msgid "Social login configuration"
-msgstr "Configuration d'un module social"
-
-#: src/pyams_security/interfaces/__init__.py:480
-#: src/pyams_security/interfaces/__init__.py:473
-msgid "Provider name"
-msgstr "Nom du fournisseur"
+#: src/pyams_security/views/userfolder.py:134
+msgid "Your registration is recorded!"
+msgstr "Votre inscription est validée !"
 
-#: src/pyams_security/interfaces/__init__.py:483
-msgid "Provider ID"
-msgstr "ID du fournisseur"
-
-#: src/pyams_security/interfaces/__init__.py:484
-msgid "This value should be unique between all providers"
-msgstr "Cette valeur numérique doit être unique pour tous les fournisseurs"
-
-#: src/pyams_security/interfaces/__init__.py:488
-msgid "Provider class"
-msgstr "Classe du fournisseur"
-
-#: src/pyams_security/interfaces/__init__.py:491
-msgid "Provider consumer key"
-msgstr "Clé cliente"
-
-#: src/pyams_security/interfaces/__init__.py:494
-msgid "Provider secret"
-msgstr "Clé secrète"
+#: src/pyams_security/views/userfolder.py:136
+msgid ""
+"Your registration is recorded. You should receive a confirmation email soon "
+"which will allow you to confirm your inscription."
+msgstr ""
+"Votre demande d'inscription est enregistrée. Vous devriez recevoir un "
+"message de confirmation prochaînement qui vous permettra de confirmer cette "
+"demande."
 
-#: src/pyams_security/interfaces/__init__.py:497
-msgid "Provider scope"
-msgstr "Portée du fournisseur"
-
-#: src/pyams_security/interfaces/__init__.py:252
-msgid "Your email address is not valid!"
-msgstr "Votre adresse de messagerie est incorrecte !"
-
-#: src/pyams_security/interfaces/__init__.py:276
-#: src/pyams_security/interfaces/__init__.py:299
-msgid "You didn't confirmed your password correctly!"
-msgstr "Vous n'avez pas confirmé votre mot de passe correctement !"
-
-#: src/pyams_security/interfaces/__init__.py:321
-msgid "Given email address is not valid!"
-msgstr "L'adrese de messagerie indiquée est invalide !"
-
-#: src/pyams_security/interfaces/__init__.py:417
-msgid "You can't activate open registration without selecting a users folder"
-msgstr ""
-"Vous ne pouvez pas activer les fonctions d'inscription sans sélectionner de "
-"dossier de stockage des utilisateurs"
-
-#: src/pyams_security/interfaces/__init__.py:474
-msgid "Provider configuration"
-msgstr "Configuration du fournisseur"
+#: src/pyams_security/views/userfolder.py:200
+msgid "Can't retrieve user profile!"
+msgstr "Impossible d'accéder à votre compte utilisateur !"
 
 #: src/pyams_security/views/login.py:90 src/pyams_security/views/login.py:149
 msgid "Please enter valid credentials to log-in"
 msgstr "Veuillez indiquer vos paramètres de connexion"
 
+#: src/pyams_security/views/login.py:57
+#: src/pyams_security/interfaces/__init__.py:338
+#: src/pyams_security/interfaces/__init__.py:364
+#: src/pyams_security/interfaces/__init__.py:416
+msgid "Password"
+msgstr "Mot de passe"
+
 #: src/pyams_security/views/login.py:63
 msgid "Reset"
 msgstr "Annuler"
@@ -428,11 +426,6 @@
 msgid "Connect"
 msgstr "Connexion"
 
-#: src/pyams_security/views/login.py:140
-#: src/pyams_security/views/userfolder.py:65
-msgid "Cancel"
-msgstr "Annuler"
-
 #: src/pyams_security/views/login.py:82
 msgid "Missing security manager utility. Please contact administrator!"
 msgstr ""
@@ -443,69 +436,388 @@
 msgid "Invalid credentials!"
 msgstr "Paramètres de connexion incorrects !"
 
-#: src/pyams_security/views/userfolder.py:53
-msgid "Register new account"
-msgstr "Créer un compte"
+#: src/pyams_security/interfaces/__init__.py:131
+msgid "Plug-in prefix"
+msgstr "Préfixe du module"
+
+#: src/pyams_security/interfaces/__init__.py:132
+msgid ""
+"This prefix is mainly used by authentication plug-ins to mark principals"
+msgstr ""
+"Ce préfixe est utilisé par les modules d'authentification pour identifier "
+"les utilisateurs"
+
+#: src/pyams_security/interfaces/__init__.py:134
+msgid "Plug-in title"
+msgstr "Libellé du module"
+
+#: src/pyams_security/interfaces/__init__.py:137
+msgid "Enabled plug-in?"
+msgstr "Module actif ?"
+
+#: src/pyams_security/interfaces/__init__.py:138
+msgid "You can choose to disable any plug-in..."
+msgstr ""
+"Un module inactif ne peut plus être utilisé pour authentifier ou rechercher "
+"les utilisateurs..."
+
+#: src/pyams_security/interfaces/__init__.py:173
+msgid "Admin. login"
+msgstr "Code utilisateur"
+
+#: src/pyams_security/interfaces/__init__.py:175
+msgid "Admin. password"
+msgstr "Mot de passe"
+
+#: src/pyams_security/interfaces/__init__.py:225
+msgid "Internal provider ID"
+msgstr "ID du connecteur"
+
+#: src/pyams_security/interfaces/__init__.py:229
+msgid "User name"
+msgstr "Nom d'utilisateur"
 
-#: src/pyams_security/views/userfolder.py:73
-msgid "User registration"
-msgstr "Inscription"
+#: src/pyams_security/interfaces/__init__.py:234
+#: src/pyams_security/interfaces/__init__.py:329
+#: src/pyams_security/interfaces/__init__.py:400
+msgid "First name"
+msgstr "Prénom"
+
+#: src/pyams_security/interfaces/__init__.py:237
+#: src/pyams_security/interfaces/__init__.py:332
+#: src/pyams_security/interfaces/__init__.py:403
+msgid "Last name"
+msgstr "Nom"
+
+#: src/pyams_security/interfaces/__init__.py:240
+msgid "Nickname"
+msgstr "Surnom"
+
+#: src/pyams_security/interfaces/__init__.py:246
+msgid "Timezone"
+msgstr "Fuseau horaire"
 
-#: src/pyams_security/views/userfolder.py:74
-msgid "Please enter registration info"
-msgstr "Veuillez indiquer les paramètres de votre compte"
+#: src/pyams_security/interfaces/__init__.py:249
+msgid "Country"
+msgstr "Pays"
+
+#: src/pyams_security/interfaces/__init__.py:252
+msgid "City"
+msgstr "Ville"
+
+#: src/pyams_security/interfaces/__init__.py:255
+msgid "Postal code"
+msgstr "Code postal"
+
+#: src/pyams_security/interfaces/__init__.py:258
+msgid "Locale code"
+msgstr "Langue"
+
+#: src/pyams_security/interfaces/__init__.py:261
+msgid "Picture URL"
+msgstr "Photo (URL)"
+
+#: src/pyams_security/interfaces/__init__.py:264
+msgid "Birth date"
+msgstr "Date de naissance"
 
-#: src/pyams_security/views/userfolder.py:155
-msgid "User registration confirmation"
-msgstr "Confirmation d'inscription"
+#: src/pyams_security/interfaces/__init__.py:300
+msgid ""
+"Your password must contain at least three of these kinds of characters: "
+"lowercase letters, uppercase letters, numbers and special characters"
+msgstr ""
+"Votre mot de passe doit contenir au moins trois de ces types de caractères : "
+"minuscules, majuscules, chiffres et autres caractères"
+
+#: src/pyams_security/interfaces/__init__.py:310
+#: src/pyams_security/interfaces/__init__.py:361
+#: src/pyams_security/interfaces/__init__.py:383
+msgid "User login"
+msgstr "Code utilisateur"
 
-#: src/pyams_security/views/userfolder.py:156
-msgid "Please confirm your registration info"
-msgstr "Veuillez confirmer les paramètres de votre compte"
+#: src/pyams_security/interfaces/__init__.py:311
+msgid ""
+"If you don't provide a custom login, your login will be your email address..."
+msgstr ""
+"Si vous n'indiquez pas de code utilisateur, vous pourrez utiliser votre "
+"adresse de messagerie pour vous connecter..."
+
+#: src/pyams_security/interfaces/__init__.py:320
+msgid ""
+"An email will be sent to this address to validate account activation; it "
+"will be used as your future user login"
+msgstr ""
+"Un message sera envoyé à cette adresse pour la valider et pour vous "
+"permettre d'activer votre compte ; il pourra être utilisé compte identifiant "
+"de connexion si vous n'avez pas indiqué de code utilisateur"
+
+#: src/pyams_security/interfaces/__init__.py:335
+#: src/pyams_security/interfaces/__init__.py:408
+msgid "Company name"
+msgstr "Société"
 
-#: src/pyams_security/views/userfolder.py:66
-msgid "Register"
-msgstr "Inscrire ce compte"
+#: src/pyams_security/interfaces/__init__.py:339
+msgid ""
+"Password must be at least 8 characters long, and contain at least three kins "
+"of characters between lowercase letters, uppercase letters, numbers and "
+"special characters"
+msgstr ""
+"Le mot de passe doit être composé d'au moins huit caractères, et contenir au "
+"moins trois types de caractères parmi les lettres minuscules, les "
+"majuscules, les chiffres et les caractères spéciaux"
+
+#: src/pyams_security/interfaces/__init__.py:345
+#: src/pyams_security/interfaces/__init__.py:368
+msgid "Confirmed password"
+msgstr "Confirmation du mot de passe"
+
+#: src/pyams_security/interfaces/__init__.py:358
+#: src/pyams_security/interfaces/__init__.py:435
+msgid "Activation hash"
+msgstr "Clé d'activation"
+
+#: src/pyams_security/interfaces/__init__.py:392
+msgid "User email address"
+msgstr "Adresse de messagerie"
 
-#: src/pyams_security/views/userfolder.py:147
-msgid "Finalize registration"
-msgstr "Terminer mon inscription"
+#: src/pyams_security/interfaces/__init__.py:411
+msgid "Password manager name"
+msgstr "Gestionnaire de mot de passe"
+
+#: src/pyams_security/interfaces/__init__.py:420
+msgid "Wait confirmation?"
+msgstr "Attendre la confirmation ?"
+
+#: src/pyams_security/interfaces/__init__.py:421
+msgid ""
+"If 'no', user will be activated immediately without waiting email "
+"confirmation"
+msgstr ""
+"Si 'non', ce compte utilisateur sera activé immédiatement sans attendre le "
+"message de confirmation"
+
+#: src/pyams_security/interfaces/__init__.py:426
+msgid "Self-registered profile?"
+msgstr "Profil auto-enregistré ?"
+
+#: src/pyams_security/interfaces/__init__.py:431
+msgid "Activation secret key"
+msgstr "Clé secrète"
+
+#: src/pyams_security/interfaces/__init__.py:432
+msgid "This private secret is used to create and check activation hash"
+msgstr ""
+"Cette clé secrète est utilisé pour créer et vérifier la clé d'activation"
 
-#: src/pyams_security/views/userfolder.py:119
-msgid "Can't create user profile. Please contact system administrator."
+#: src/pyams_security/interfaces/__init__.py:436
+msgid ""
+"This hash is provided into activation message URL. Activation hash is "
+"missing for local users which were registered without waiting their "
+"confirmation."
 msgstr ""
-"Impossible de créer votre compte utilisateur. Veuillez contacter "
-"l'administrateur."
+"Cette clé est fournie dans le message de confirmation de l'inscription. "
+"Cette clé d'activation n'est pas définie pour les utilisateurs pour lesquels "
+"l'attente de confirmation n'a pas été demandée."
+
+#: src/pyams_security/interfaces/__init__.py:476
+msgid "Group ID"
+msgstr "ID du groupe"
+
+#: src/pyams_security/interfaces/__init__.py:477
+msgid "This ID should be unique between all groups"
+msgstr "Cet ID doit être unique pour tous les groupes"
+
+#: src/pyams_security/interfaces/__init__.py:481
+msgid "Title"
+msgstr "Nom"
+
+#: src/pyams_security/interfaces/__init__.py:482
+msgid "Public label of this group"
+msgstr "Libellé public de ce groupe"
+
+#: src/pyams_security/interfaces/__init__.py:488
+msgid "Group principals"
+msgstr "Mandants du groupe"
+
+#: src/pyams_security/interfaces/__init__.py:489
+msgid "IDs of principals contained in this group"
+msgstr "ID des mandants contenus dans ce groupe"
+
+#: src/pyams_security/interfaces/__init__.py:539
+msgid "Enable login via social OAuth plug-ins"
+msgstr ""
+"Autoriser la connexion à partir des réseaux sociaux via les modules OAuth"
+
+#: src/pyams_security/interfaces/__init__.py:543
+msgid "Social users folder"
+msgstr "Dossier des utilisateurs \"sociaux\""
 
-#: src/pyams_security/views/userfolder.py:193
-msgid "Can't check user profile. Please contact system administrator."
+#: src/pyams_security/interfaces/__init__.py:544
+msgid "Name of folder used to store social users properties"
+msgstr ""
+"Nom du dossier dans lequel seront stockés les profils des utilisateurs "
+"connectés via des réseaux sociaux"
+
+#: src/pyams_security/interfaces/__init__.py:553
+msgid "Authomatic secret"
+msgstr "Clé OAuth"
+
+#: src/pyams_security/interfaces/__init__.py:554
+msgid "This secret phrase is used to encrypt Authomatic cookie"
 msgstr ""
-"Impossible de vérifier votre compte utilisateur. Veuillez contacter "
-"l'administrateur."
+"Cette phrase secrète est utilisée pour crypter le cookie d'authentification"
+
+#: src/pyams_security/interfaces/__init__.py:558
+msgid "Use social popup?"
+msgstr "Connection via une popup ?"
+
+#: src/pyams_security/interfaces/__init__.py:563
+msgid "If 'Yes', any use will be able to create a new user account"
+msgstr "Si 'oui', toute personne sera à même de se créer un compte utilisateur"
+
+#: src/pyams_security/interfaces/__init__.py:567
+msgid "Users folder"
+msgstr "Dossier des utilisateurs locaux"
+
+#: src/pyams_security/interfaces/__init__.py:568
+msgid "Name of users folder used to store registered principals"
+msgstr "Nom du dossier dans lequel seront créés les utilisateurs enregistrés"
+
+#: src/pyams_security/interfaces/__init__.py:577
+msgid "Credentials plug-ins"
+msgstr "Modules d'identification"
+
+#: src/pyams_security/interfaces/__init__.py:578
+msgid "These plug-ins can be used to extract request credentials"
+msgstr ""
+"Ces modules peuvent être utilisés pour extraire l'identitité des utilisateurs"
+
+#: src/pyams_security/interfaces/__init__.py:583
+msgid "Authentication plug-ins"
+msgstr "Modules d'authentification"
 
-#: src/pyams_security/views/userfolder.py:132
-msgid "Your registration is recorded!"
-msgstr "Votre inscription est validée !"
-
-#: src/pyams_security/views/userfolder.py:134
+#: src/pyams_security/interfaces/__init__.py:584
 msgid ""
-"Your registration is recorded. You should receive a confirmation email soon "
-"which will allow you to confirm your inscription."
+"The plug-ins can be used to check extracted credentials against a local or "
+"remote users database"
+msgstr ""
+"Ces modules sont utilisés pour vérifier l'identité des utilisateurs vis à "
+"vis d'une base d'utilisateurs locale ou distante"
+
+#: src/pyams_security/interfaces/__init__.py:589
+msgid "Directory plug-ins"
+msgstr "Modules d'annuaires"
+
+#: src/pyams_security/interfaces/__init__.py:590
+msgid "The plug-in can be used to extract principals information"
 msgstr ""
-"Votre demande d'inscription est enregistrée. Vous devriez recevoir un "
-"message de confirmation prochaînement qui vous permettra de confirmer cette "
-"demande."
+"Ces modules peuvent être utilisés pour extraire les propriétés des "
+"utilisateurs"
+
+#: src/pyams_security/interfaces/__init__.py:664
+msgid "Provider name"
+msgstr "Nom du fournisseur"
+
+#: src/pyams_security/interfaces/__init__.py:668
+msgid "Provider ID"
+msgstr "ID du fournisseur"
+
+#: src/pyams_security/interfaces/__init__.py:669
+msgid "This value should be unique between all providers"
+msgstr "Cette valeur numérique doit être unique pour tous les fournisseurs"
+
+#: src/pyams_security/interfaces/__init__.py:673
+msgid "Provider consumer key"
+msgstr "Clé cliente"
+
+#: src/pyams_security/interfaces/__init__.py:676
+msgid "Provider secret"
+msgstr "Clé secrète"
+
+#: src/pyams_security/interfaces/__init__.py:694
+msgid "Inherit parent security?"
+msgstr "Héritage de la sécurité ?"
+
+#: src/pyams_security/interfaces/__init__.py:695
+msgid "Get access control entries (ACE) inherited from parent levels"
+msgstr "Utiliser les règles d'accès (ACE) héritées du parent"
+
+#: src/pyams_security/interfaces/__init__.py:700
+msgid "Public permission"
+msgstr "Permission publique"
+
+#: src/pyams_security/interfaces/__init__.py:701
+msgid "This permission will be granted to all users"
+msgstr "Cette permission sera attribuée à tous les utilisateurs"
 
-#: src/pyams_security/views/userfolder.py:198
-msgid "Can't retrieve user profile!"
-msgstr "Impossible d'accéder à votre compte utilisateur !"
+#: src/pyams_security/interfaces/__init__.py:705
+msgid "Authenticated permission"
+msgstr "Permission authentifiée"
+
+#: src/pyams_security/interfaces/__init__.py:706
+msgid "This permission will be granted to authenticated users"
+msgstr "Cette permission sera attribuée à tous les utilisateurs authentifiés"
+
+#: src/pyams_security/interfaces/__init__.py:710
+msgid "Inherit parent roles?"
+msgstr "Héritage des rôles ?"
+
+#: src/pyams_security/interfaces/__init__.py:711
+msgid "Get roles granted on parent levels"
+msgstr "Disposer des rôles affectés aux niveaux parents"
+
+#: src/pyams_security/interfaces/__init__.py:327
+msgid "Your email address is not valid!"
+msgstr "Votre adresse de messagerie est incorrecte !"
+
+#: src/pyams_security/interfaces/__init__.py:351
+#: src/pyams_security/interfaces/__init__.py:374
+msgid "You didn't confirmed your password correctly!"
+msgstr "Vous n'avez pas confirmé votre mot de passe correctement !"
+
+#: src/pyams_security/interfaces/__init__.py:398
+msgid "Given email address is not valid!"
+msgstr "L'adrese de messagerie indiquée est invalide !"
+
+#: src/pyams_security/interfaces/__init__.py:551
+msgid "You can't activate social login without selecting a social users folder"
+msgstr ""
+"Vous ne pouvez pas activer la connexion via les réseaux sociaux sans "
+"sélectionner de dossier de stockage des utilisateurs"
+
+#: src/pyams_security/interfaces/__init__.py:575
+msgid "You can't activate open registration without selecting a users folder"
+msgstr ""
+"Vous ne pouvez pas activer les fonctions d'inscription libre sans "
+"sélectionner de dossier de stockage des utilisateurs"
 
 #: src/pyams_security/plugin/http.py:44
 msgid "HTTP Basic credentials"
 msgstr "Authentification HTTP Basic"
 
-#: src/pyams_security/plugin/userfolder.py:106
-#: src/pyams_security/plugin/userfolder.py:111
+#: src/pyams_security/plugin/userfolder.py:105
+#: src/pyams_security/plugin/userfolder.py:110
 msgid "Can't activate profile with given params!"
 msgstr ""
-"Impossible de confirmer votre inscription avec les paramètres indiqués !"
+"Impossible de confirmer votre inscription avec les paramètres fournis !"
+
+#~ msgid "Social login configuration"
+#~ msgstr "Configuration d'un module social"
+
+#~ msgid "Provider class"
+#~ msgstr "Classe du fournisseur"
+
+#~ msgid "Provider scope"
+#~ msgstr "Portée du fournisseur"
+
+#~ msgid "Provider configuration"
+#~ msgstr "Configuration du fournisseur"
+
+#~ msgid "Clear search criteria"
+#~ msgstr "Effacer les critères de recherche"
+
+#~ msgid "List of principals IDs"
+#~ msgstr "Liste des ID des mandants du groupe"
+
+#~ msgid "Open registration?"
+#~ msgstr "Inscription ouverte ?"
--- a/src/pyams_security/locales/pyams_security.pot	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/locales/pyams_security.pot	Mon Feb 23 17:55:05 2015 +0100
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-19 00:29+0100\n"
+"POT-Creation-Date: 2015-02-23 17:19+0100\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,44 +16,312 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Lingua 3.8\n"
 
-#: ./src/pyams_security/principal.py:49
+#: ./src/pyams_security/__init__.py:41
+msgid "View public contents"
+msgstr ""
+
+#: ./src/pyams_security/__init__.py:43
+msgid "View protected contents"
+msgstr ""
+
+#: ./src/pyams_security/__init__.py:45
+msgid "Manage properties contents"
+msgstr ""
+
+#: ./src/pyams_security/__init__.py:47
+msgid "View management screens"
+msgstr ""
+
+#: ./src/pyams_security/__init__.py:49
+msgid "Manage system"
+msgstr ""
+
+#: ./src/pyams_security/principal.py:52
 msgid "Not logged in"
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:73
+#: ./src/pyams_security/widget/templates/principals-set-input.pt:3
+#: ./src/pyams_security/widget/templates/principal-input.pt:3
+msgid "Clear selected values"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:77
 msgid "Security"
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:82
+#: ./src/pyams_security/zmi/utility.py:86
 msgid "Authentication and users directory plug-ins"
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:127
+#: ./src/pyams_security/zmi/utility.py:108
+msgid "See plug-in contents"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:123
+#: ./src/pyams_security/zmi/plugin/social.py:186
+#: ./src/pyams_security/zmi/plugin/social.py:320
+#: ./src/pyams_security/zmi/plugin/userfolder.py:184
+#: ./src/pyams_security/zmi/plugin/group.py:151
+#: ./src/pyams_security/interfaces/__init__.py:232
+msgid "Name"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:134
+msgid "Delete plug-in"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:175
+#: ./src/pyams_security/zmi/plugin/social.py:368
+#: ./src/pyams_security/zmi/plugin/social.py:386
+#: ./src/pyams_security/zmi/plugin/social.py:441
 msgid "Security manager"
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:128
+#: ./src/pyams_security/zmi/utility.py:176
 msgid "Security manager plug-ins"
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:139
+#: ./src/pyams_security/zmi/utility.py:187
 msgid "Add..."
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:147
+#: ./src/pyams_security/zmi/utility.py:195
 msgid "Properties..."
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:160
+#: ./src/pyams_security/zmi/utility.py:208
+#: ./src/pyams_security/zmi/plugin/social.py:79
+#: ./src/pyams_security/zmi/plugin/userfolder.py:77
+#: ./src/pyams_security/zmi/plugin/group.py:73
 #: ./src/pyams_security/zmi/plugin/admin.py:61
-#: ./src/pyams_security/zmi/plugin/userfolder.py:74
 msgid "System security manager"
 msgstr ""
 
-#: ./src/pyams_security/zmi/utility.py:161
+#: ./src/pyams_security/zmi/utility.py:209
 msgid "Security manager properties"
 msgstr ""
 
+#: ./src/pyams_security/zmi/utility.py:246
+msgid "Plug-ins"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:231
+#: ./src/pyams_security/interfaces/__init__.py:538
+msgid "Enable social login?"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:238
+#: ./src/pyams_security/interfaces/__init__.py:562
+msgid "Enable free registration?"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:268
+msgid "No provided plugin_name argument!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/utility.py:272
+msgid "Given plug-in name doesn't exist!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/security.py:41
+msgid "Access rules..."
+msgstr ""
+
+#: ./src/pyams_security/zmi/security.py:56
+msgid "Edit local roles"
+msgstr ""
+
+#: ./src/pyams_security/zmi/security.py:72
+#: ./src/pyams_security/zmi/security.py:73
+msgid "(inherit from parent)"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:68
+msgid "Add social users folder..."
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:80
+msgid "Add social users folder plug-in"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:113
+msgid "Edit social users folder plug-in properties"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:154
+#: ./src/pyams_security/zmi/plugin/userfolder.py:152
+msgid "Search users"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:163
+#: ./src/pyams_security/zmi/plugin/userfolder.py:161
+msgid "Search results"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:176
+msgid "User ID"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:196
+#: ./src/pyams_security/zmi/plugin/userfolder.py:194
+#: ./src/pyams_security/interfaces/__init__.py:243
+#: ./src/pyams_security/interfaces/__init__.py:319
+msgid "E-mail address"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:206
+#: ./src/pyams_security/interfaces/__init__.py:227
+msgid "OAuth provider name"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:216
+#: ./src/pyams_security/zmi/plugin/userfolder.py:204
+#: ./src/pyams_security/interfaces/__init__.py:267
+msgid "Registration date"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:239
+#: ./src/pyams_security/zmi/plugin/userfolder.py:324
+msgid "Edit user properties"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:271
+msgid "Social networks login"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:279
+msgid "Configured social networks login providers"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:310
+msgid "ID"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:331
+msgid "Delete provider"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:369
+msgid "Social networks login providers"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:377
+msgid "Add provider"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:387
+msgid "Add new social login provider"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:442
+msgid "Edit social login provider properties"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:417
+msgid "This provider is already defined!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:433
+msgid "Social provider was created successfully"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:420
+msgid "This provider ID is already used!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:473
+msgid "No provided provider_name argument!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/social.py:477
+msgid "Given provider name doesn't exist!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:66
+msgid "Add local users folder..."
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:78
+msgid "Add local users folder plug-in"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:111
+msgid "Edit local users folder plug-in properties"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:174
+#: ./src/pyams_security/views/login.py:56
+msgid "Login"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:220
+#: ./src/pyams_security/interfaces/__init__.py:441
+#: ./src/pyams_security/interfaces/__init__.py:444
+msgid "Activation date"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:240
+msgid "Add user"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:253
+msgid "Add new local user"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:302
+#: ./src/pyams_security/views/userfolder.py:123
+msgid "Specified login can't be used!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/userfolder.py:313
+msgid "User was created successfully"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:62
+msgid "Add local groups folder..."
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:74
+msgid "Add local groups folder plug-in"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:107
+msgid "Edit local groups folder plug-in properties"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:133
+msgid "Local groups"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:161
+#: ./src/pyams_security/interfaces/__init__.py:485
+msgid "Description"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:191
+msgid "Groups list"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:204
+msgid "Add group"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:217
+msgid "Add new local group"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:273
+msgid "Edit group properties"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:250
+msgid "Specified group ID can't be used!"
+msgstr ""
+
+#: ./src/pyams_security/zmi/plugin/group.py:262
+msgid "Group was created successfully"
+msgstr ""
+
 #: ./src/pyams_security/zmi/plugin/admin.py:50
 msgid "Add admin authentication..."
 msgstr ""
@@ -76,341 +344,6 @@
 "access!"
 msgstr ""
 
-#: ./src/pyams_security/zmi/plugin/userfolder.py:63
-msgid "Add local users folder..."
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:75
-msgid "Add local users folder plug-in"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:108
-msgid "Edit local users folder plug-in properties"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:158
-msgid "Search users"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:167
-msgid "Search results"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:180
-#: ./src/pyams_security/views/login.py:56
-msgid "Login"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:194
-msgid "Name"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:208
-#: ./src/pyams_security/interfaces/__init__.py:244
-msgid "E-mail address"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:222
-msgid "Registration date"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:242
-#: ./src/pyams_security/interfaces/__init__.py:364
-#: ./src/pyams_security/interfaces/__init__.py:367
-msgid "Activation date"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:262
-msgid "Add user"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:275
-msgid "Add new local user"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:346
-msgid "Edit user properties"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:324
-#: ./src/pyams_security/views/userfolder.py:121
-msgid "Specified login can't be used!"
-msgstr ""
-
-#: ./src/pyams_security/zmi/plugin/userfolder.py:335
-msgid "User was created successfully"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:130
-msgid "Plug-in prefix"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:131
-msgid ""
-"This prefix is mainly used by authentication plug-ins to mark principals"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:133
-msgid "Plug-in title"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:136
-msgid "Enabled plug-in?"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:137
-msgid "You can choose to disable any plug-in..."
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:172
-msgid "Admin. login"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:174
-msgid "Admin. password"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:225
-msgid ""
-"Your password must contain at least three of these kinds of characters: "
-"lowercase letters, uppercase letters, numbers and special characters"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:235
-#: ./src/pyams_security/interfaces/__init__.py:286
-#: ./src/pyams_security/interfaces/__init__.py:306
-msgid "User login"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:236
-msgid ""
-"If you don't provide a custom login, your login will be your email address..."
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:245
-msgid ""
-"An email will be sent to this address to validate account activation; it will"
-" be used as your future user login"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:254
-#: ./src/pyams_security/interfaces/__init__.py:323
-msgid "First name"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:257
-#: ./src/pyams_security/interfaces/__init__.py:326
-msgid "Last name"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:260
-#: ./src/pyams_security/interfaces/__init__.py:331
-msgid "Company name"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:263
-#: ./src/pyams_security/interfaces/__init__.py:289
-#: ./src/pyams_security/interfaces/__init__.py:339
-#: ./src/pyams_security/views/login.py:57
-msgid "Password"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:264
-msgid ""
-"Password must be at least 8 characters long, and contain at least three kins "
-"of characters between lowercase letters, uppercase letters, numbers and "
-"special characters"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:270
-#: ./src/pyams_security/interfaces/__init__.py:293
-msgid "Confirmed password"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:283
-#: ./src/pyams_security/interfaces/__init__.py:358
-msgid "Activation hash"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:315
-msgid "User email address"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:334
-msgid "Password manager name"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:343
-msgid "Wait confirmation?"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:344
-msgid ""
-"If 'no', user will be activated immediately without waiting email "
-"confirmation"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:349
-msgid "Self-registered profile?"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:354
-msgid "Activation secret key"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:355
-msgid "This private secret is used to create and check activation hash"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:359
-msgid ""
-"This hash is provided into activation message URL. Activation hash is missing"
-" for local users which were registered without waiting their confirmation."
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:390
-msgid "Enable social login?"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:391
-msgid "Enable login via social OAuth plug-ins"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:395
-msgid "Authomatic secret"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:396
-msgid "This secret phrase is used to encrypt Authomatic cookie"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:400
-msgid "Use social popup?"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:404
-msgid "Open registration?"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:405
-msgid "If 'Yes', any use will be able to create a new user account"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:409
-msgid "Users folder"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:410
-msgid "Name of users folder used to store registered principals"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:419
-msgid "Credentials plug-ins"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:420
-msgid "These plug-ins can be used to extract request credentials"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:425
-msgid "Authentication plug-ins"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:426
-msgid ""
-"The plug-ins can be used to check extracted credentials against a local or "
-"remote users database"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:431
-msgid "Directory plug-ins"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:432
-msgid "The plug-in can be used to extract principals information"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:472
-msgid "Social login configuration"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:480
-#: ./src/pyams_security/interfaces/__init__.py:473
-msgid "Provider name"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:483
-msgid "Provider ID"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:484
-msgid "This value should be unique between all providers"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:488
-msgid "Provider class"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:491
-msgid "Provider consumer key"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:494
-msgid "Provider secret"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:497
-msgid "Provider scope"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:252
-msgid "Your email address is not valid!"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:276
-#: ./src/pyams_security/interfaces/__init__.py:299
-msgid "You didn't confirmed your password correctly!"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:321
-msgid "Given email address is not valid!"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:417
-msgid "You can't activate open registration without selecting a users folder"
-msgstr ""
-
-#: ./src/pyams_security/interfaces/__init__.py:474
-msgid "Provider configuration"
-msgstr ""
-
-#: ./src/pyams_security/views/login.py:90
-#: ./src/pyams_security/views/login.py:149
-msgid "Please enter valid credentials to log-in"
-msgstr ""
-
-#: ./src/pyams_security/views/login.py:63
-msgid "Reset"
-msgstr ""
-
-#: ./src/pyams_security/views/login.py:64
-#: ./src/pyams_security/views/login.py:141
-msgid "Connect"
-msgstr ""
-
-#: ./src/pyams_security/views/login.py:140
-#: ./src/pyams_security/views/userfolder.py:65
-msgid "Cancel"
-msgstr ""
-
-#: ./src/pyams_security/views/login.py:82
-msgid "Missing security manager utility. Please contact administrator!"
-msgstr ""
-
-#: ./src/pyams_security/views/login.py:78
-msgid "Invalid credentials!"
-msgstr ""
-
 #: ./src/pyams_security/views/userfolder.py:53
 msgid "Register new account"
 msgstr ""
@@ -423,49 +356,404 @@
 msgid "Please enter registration info"
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:155
+#: ./src/pyams_security/views/userfolder.py:157
 msgid "User registration confirmation"
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:156
+#: ./src/pyams_security/views/userfolder.py:158
 msgid "Please confirm your registration info"
 msgstr ""
 
+#: ./src/pyams_security/views/userfolder.py:65
+#: ./src/pyams_security/views/login.py:140
+msgid "Cancel"
+msgstr ""
+
 #: ./src/pyams_security/views/userfolder.py:66
 msgid "Register"
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:147
+#: ./src/pyams_security/views/userfolder.py:149
 msgid "Finalize registration"
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:119
+#: ./src/pyams_security/views/userfolder.py:121
 msgid "Can't create user profile. Please contact system administrator."
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:193
+#: ./src/pyams_security/views/userfolder.py:195
 msgid "Can't check user profile. Please contact system administrator."
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:132
+#: ./src/pyams_security/views/userfolder.py:134
 msgid "Your registration is recorded!"
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:134
+#: ./src/pyams_security/views/userfolder.py:136
 msgid ""
 "Your registration is recorded. You should receive a confirmation email soon "
 "which will allow you to confirm your inscription."
 msgstr ""
 
-#: ./src/pyams_security/views/userfolder.py:198
+#: ./src/pyams_security/views/userfolder.py:200
 msgid "Can't retrieve user profile!"
 msgstr ""
 
+#: ./src/pyams_security/views/login.py:90
+#: ./src/pyams_security/views/login.py:149
+msgid "Please enter valid credentials to log-in"
+msgstr ""
+
+#: ./src/pyams_security/views/login.py:57
+#: ./src/pyams_security/interfaces/__init__.py:338
+#: ./src/pyams_security/interfaces/__init__.py:364
+#: ./src/pyams_security/interfaces/__init__.py:416
+msgid "Password"
+msgstr ""
+
+#: ./src/pyams_security/views/login.py:63
+msgid "Reset"
+msgstr ""
+
+#: ./src/pyams_security/views/login.py:64
+#: ./src/pyams_security/views/login.py:141
+msgid "Connect"
+msgstr ""
+
+#: ./src/pyams_security/views/login.py:82
+msgid "Missing security manager utility. Please contact administrator!"
+msgstr ""
+
+#: ./src/pyams_security/views/login.py:78
+msgid "Invalid credentials!"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:131
+msgid "Plug-in prefix"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:132
+msgid ""
+"This prefix is mainly used by authentication plug-ins to mark principals"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:134
+msgid "Plug-in title"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:137
+msgid "Enabled plug-in?"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:138
+msgid "You can choose to disable any plug-in..."
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:173
+msgid "Admin. login"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:175
+msgid "Admin. password"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:225
+msgid "Internal provider ID"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:229
+msgid "User name"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:234
+#: ./src/pyams_security/interfaces/__init__.py:329
+#: ./src/pyams_security/interfaces/__init__.py:400
+msgid "First name"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:237
+#: ./src/pyams_security/interfaces/__init__.py:332
+#: ./src/pyams_security/interfaces/__init__.py:403
+msgid "Last name"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:240
+msgid "Nickname"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:246
+msgid "Timezone"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:249
+msgid "Country"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:252
+msgid "City"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:255
+msgid "Postal code"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:258
+msgid "Locale code"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:261
+msgid "Picture URL"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:264
+msgid "Birth date"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:300
+msgid ""
+"Your password must contain at least three of these kinds of characters: "
+"lowercase letters, uppercase letters, numbers and special characters"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:310
+#: ./src/pyams_security/interfaces/__init__.py:361
+#: ./src/pyams_security/interfaces/__init__.py:383
+msgid "User login"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:311
+msgid ""
+"If you don't provide a custom login, your login will be your email address..."
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:320
+msgid ""
+"An email will be sent to this address to validate account activation; it will"
+" be used as your future user login"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:335
+#: ./src/pyams_security/interfaces/__init__.py:408
+msgid "Company name"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:339
+msgid ""
+"Password must be at least 8 characters long, and contain at least three kins "
+"of characters between lowercase letters, uppercase letters, numbers and "
+"special characters"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:345
+#: ./src/pyams_security/interfaces/__init__.py:368
+msgid "Confirmed password"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:358
+#: ./src/pyams_security/interfaces/__init__.py:435
+msgid "Activation hash"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:392
+msgid "User email address"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:411
+msgid "Password manager name"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:420
+msgid "Wait confirmation?"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:421
+msgid ""
+"If 'no', user will be activated immediately without waiting email "
+"confirmation"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:426
+msgid "Self-registered profile?"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:431
+msgid "Activation secret key"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:432
+msgid "This private secret is used to create and check activation hash"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:436
+msgid ""
+"This hash is provided into activation message URL. Activation hash is missing"
+" for local users which were registered without waiting their confirmation."
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:476
+msgid "Group ID"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:477
+msgid "This ID should be unique between all groups"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:481
+msgid "Title"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:482
+msgid "Public label of this group"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:488
+msgid "Group principals"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:489
+msgid "IDs of principals contained in this group"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:539
+msgid "Enable login via social OAuth plug-ins"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:543
+msgid "Social users folder"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:544
+msgid "Name of folder used to store social users properties"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:553
+msgid "Authomatic secret"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:554
+msgid "This secret phrase is used to encrypt Authomatic cookie"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:558
+msgid "Use social popup?"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:563
+msgid "If 'Yes', any use will be able to create a new user account"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:567
+msgid "Users folder"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:568
+msgid "Name of users folder used to store registered principals"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:577
+msgid "Credentials plug-ins"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:578
+msgid "These plug-ins can be used to extract request credentials"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:583
+msgid "Authentication plug-ins"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:584
+msgid ""
+"The plug-ins can be used to check extracted credentials against a local or "
+"remote users database"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:589
+msgid "Directory plug-ins"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:590
+msgid "The plug-in can be used to extract principals information"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:664
+msgid "Provider name"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:668
+msgid "Provider ID"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:669
+msgid "This value should be unique between all providers"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:673
+msgid "Provider consumer key"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:676
+msgid "Provider secret"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:694
+msgid "Inherit parent security?"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:695
+msgid "Get access control entries (ACE) inherited from parent levels"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:700
+msgid "Public permission"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:701
+msgid "This permission will be granted to all users"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:705
+msgid "Authenticated permission"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:706
+msgid "This permission will be granted to authenticated users"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:710
+msgid "Inherit parent roles?"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:711
+msgid "Get roles granted on parent levels"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:327
+msgid "Your email address is not valid!"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:351
+#: ./src/pyams_security/interfaces/__init__.py:374
+msgid "You didn't confirmed your password correctly!"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:398
+msgid "Given email address is not valid!"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:551
+msgid "You can't activate social login without selecting a social users folder"
+msgstr ""
+
+#: ./src/pyams_security/interfaces/__init__.py:575
+msgid "You can't activate open registration without selecting a users folder"
+msgstr ""
+
 #: ./src/pyams_security/plugin/http.py:44
 msgid "HTTP Basic credentials"
 msgstr ""
 
-#: ./src/pyams_security/plugin/userfolder.py:106
-#: ./src/pyams_security/plugin/userfolder.py:111
+#: ./src/pyams_security/plugin/userfolder.py:105
+#: ./src/pyams_security/plugin/userfolder.py:110
 msgid "Can't activate profile with given params!"
 msgstr ""
--- a/src/pyams_security/permission.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/permission.py	Mon Feb 23 17:55:05 2015 +0100
@@ -16,10 +16,13 @@
 
 # import interfaces
 from pyams_security.interfaces import IPermission
+from zope.schema.interfaces import IVocabularyFactory
 
 # import packages
-from zope.interface import implementer
+from pyams_utils.request import check_request
+from zope.interface import implementer, provider
 from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import getVocabularyRegistry, SimpleTerm, SimpleVocabulary
 
 
 @implementer(IPermission)
@@ -48,3 +51,21 @@
     if not IPermission.providedBy(permission):
         permission = Permission(permission)
     config.registry.registerUtility(permission, IPermission, name=permission.id)
+
+
+@provider(IVocabularyFactory)
+class PermissionsVocabulary(SimpleVocabulary):
+    """Permissions vocabulary"""
+
+    interface = IPermission
+
+    def __init__(self, *args, **kwargs):
+        request = check_request()
+        registry = request.registry
+        translate = request.localizer.translate
+        terms = [SimpleTerm(p.id, title=translate(p.title))
+                 for n, p in registry.getUtilitiesFor(self.interface)]
+        terms.sort(key=lambda x: x.title)
+        super(PermissionsVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS permissions', PermissionsVocabulary)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/plugin/group.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,153 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import IGroupsFolderPlugin, ILocalGroup, IPrincipalsAddedToGroupEvent, \
+    IPrincipalsRemovedFromGroupEvent, PrincipalsAddedToGroupEvent, PrincipalsRemovedFromGroupEvent
+from zope.lifecycleevent.interfaces import IObjectAddedEvent
+
+# import packages
+from BTrees import OOBTree
+from persistent import Persistent
+from pyams_security.principal import PrincipalInfo
+from pyams_utils.request import check_request
+from pyramid.events import subscriber
+from zope.container.contained import Contained
+from zope.container.folder import Folder
+from zope.interface import implementer
+from zope.schema.fieldproperty import FieldProperty
+
+
+@implementer(ILocalGroup)
+class Group(Persistent, Contained):
+    """Local group persistent class"""
+
+    group_id = FieldProperty(ILocalGroup['group_id'])
+    title = FieldProperty(ILocalGroup['title'])
+    description = FieldProperty(ILocalGroup['description'])
+
+    _principals = FieldProperty(ILocalGroup['principals'])
+
+    @property
+    def principals(self):
+        return self._principals or set()
+
+    @principals.setter
+    def principals(self, value):
+        added = value - self._principals
+        removed = self._principals - value
+        if added or removed:
+            self._principals = value
+            registry = check_request().registry
+            if added:
+                registry.notify(PrincipalsAddedToGroupEvent(self, added))
+            if removed:
+                registry.notify(PrincipalsRemovedFromGroupEvent(self, removed))
+
+
+@implementer(IGroupsFolderPlugin)
+class GroupsFolder(Folder):
+    """Principals groups folder"""
+
+    prefix = FieldProperty(IGroupsFolderPlugin['prefix'])
+    title = FieldProperty(IGroupsFolderPlugin['title'])
+    enabled = FieldProperty(IGroupsFolderPlugin['enabled'])
+
+    def __init__(self):
+        super(GroupsFolder, self).__init__()
+        self.groups_by_principal = OOBTree.OOBTree()
+
+    def check_group_id(self, group_id):
+        """Check for existence of given group ID"""
+        if not group_id:
+            return False
+        return group_id not in self
+
+    def get_principal(self, principal_id):
+        if not self.enabled:
+            return None
+        if not principal_id.startswith(self.prefix + ':'):
+            return None
+        prefix, group_id = principal_id.split(':', 1)
+        group = self.get(group_id)
+        if group is not None:
+            return PrincipalInfo(id='{0}:{1}'.format(self.prefix, group.group_id),
+                                 title=group.title)
+
+    def get_all_principals(self, principal_id):
+        if not self.enabled:
+            return set()
+        return self.groups_by_principal.get(principal_id) or set()
+
+    def find_principals(self, query):
+        if not self.enabled:
+            raise StopIteration
+        if not query:
+            return None
+        query = query.lower()
+        for group in self.values():
+            if query in group.title.lower():
+                yield PrincipalInfo(id='{0}:{1}'.format(self.prefix, group.group_id),
+                                    title=group.title)
+
+
+@subscriber(IObjectAddedEvent, context_selector=ILocalGroup)
+def handle_added_group(event):
+    """Handle added group"""
+    group = event.object
+    folder = event.newParent
+    principals_map = folder.groups_by_principal
+    for principal_id in group.principals:
+        groups_set = principals_map.get(principal_id)
+        if groups_set is None:
+            groups_set = set()
+        group_id = '{0}:{1}'.format(folder.prefix, group.group_id)
+        groups_set.add(group_id)
+        principals_map[principal_id] = groups_set
+
+
+@subscriber(IPrincipalsAddedToGroupEvent)
+def handle_added_principals(event):
+    """Handle principals added to group"""
+    group = event.group
+    if group.__parent__ is None:  # can occur when a group is created
+        return
+    principals_map = group.__parent__.groups_by_principal
+    for principal_id in event.principals:
+        groups_set = principals_map.get(principal_id)
+        if groups_set is None:
+            groups_set = set()
+        group_id = '{0}:{1}'.format(group.__parent__.prefix, group.group_id)
+        groups_set.add(group_id)
+        principals_map[principal_id] = groups_set
+
+
+@subscriber(IPrincipalsRemovedFromGroupEvent)
+def handle_removed_principals(event):
+    """Handle principals removed from group"""
+    group = event.group
+    principals_map = group.__parent__.groups_by_principal
+    for principal_id in event.principals:
+        groups_set = principals_map.get(principal_id)
+        if groups_set:
+            group_id = '{0}:{1}'.format(group.__parent__.prefix, group.group_id)
+            if group_id in groups_set:
+                groups_set.remove(group_id)
+            if groups_set:
+                principals_map[principal_id] = groups_set
+            else:
+                del principals_map[principal_id]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/plugin/social.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,410 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from datetime import datetime
+
+# import interfaces
+from pyams_security.interfaces import ISocialUser, IPrincipalInfo, ISocialUsersFolderPlugin, ISecurityManager, \
+    IAuthenticatedPrincipalEvent, ISocialLoginConfiguration, ISocialLoginProviderInfo, ISocialLoginProviderConnection
+from zope.annotation.interfaces import IAnnotations
+from zope.schema.interfaces import IVocabularyRegistry, IVocabularyFactory
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from authomatic.providers import oauth1, oauth2
+from persistent import Persistent
+from pyams_security.principal import PrincipalInfo
+from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.registry import query_utility
+from pyams_utils.request import check_request
+from pyramid.events import subscriber
+from zope.container.contained import Contained
+from zope.container.folder import Folder
+from zope.interface import implementer, provider
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate
+from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry
+
+
+@implementer(ISocialUser)
+class SocialUser(Persistent, Contained):
+    """Social user persistent class"""
+
+    user_id = FieldProperty(ISocialUser['user_id'])
+    provider_name = FieldProperty(ISocialUser['provider_name'])
+    username = FieldProperty(ISocialUser['username'])
+    name = FieldProperty(ISocialUser['name'])
+    first_name = FieldProperty(ISocialUser['first_name'])
+    last_name = FieldProperty(ISocialUser['last_name'])
+    nickname = FieldProperty(ISocialUser['nickname'])
+    email = FieldProperty(ISocialUser['email'])
+    timezone = FieldProperty(ISocialUser['timezone'])
+    country = FieldProperty(ISocialUser['country'])
+    city = FieldProperty(ISocialUser['city'])
+    postal_code = FieldProperty(ISocialUser['postal_code'])
+    locale = FieldProperty(ISocialUser['locale'])
+    picture = FieldProperty(ISocialUser['picture'])
+    birth_date = FieldProperty(ISocialUser['birth_date'])
+    registration_date = FieldProperty(ISocialUser['registration_date'])
+
+    @property
+    def title(self):
+        if self.name:
+            result = self.name
+        elif self.first_name:
+            result = '{first} {last}'.format(self.first_name, self.last_name or '')
+        elif self.username:
+            result = self.username
+        else:
+            result = self.nickname or self.user_id
+        return result
+
+    @property
+    def title_with_source(self):
+        return '{title} ({provider})'.format(title=self.title,
+                                             provider=self.provider_name.capitalize())
+
+
+@adapter_config(context=ISocialUser, provides=IPrincipalInfo)
+def SocialUserPrincipalInfoAdapter(user):
+    """Social user principal info adapter"""
+    return PrincipalInfo(id="{0}:{1}".format(user.__parent__.prefix, user.user_id),
+                         title=user.name)
+
+
+@implementer(ISocialUsersFolderPlugin)
+class SocialUsersFolder(Folder):
+    """Social users folder"""
+
+    prefix = FieldProperty(ISocialUsersFolderPlugin['prefix'])
+    title = FieldProperty(ISocialUsersFolderPlugin['title'])
+    enabled = FieldProperty(ISocialUsersFolderPlugin['enabled'])
+
+    def get_principal(self, principal_id):
+        if not self.enabled:
+            return None
+        if not principal_id.startswith(self.prefix + ':'):
+            return None
+        prefix, login = principal_id.split(':', 1)
+        user = self.get(login)
+        if user is not None:
+            return PrincipalInfo(id='{prefix}:{user_id}'.format(prefix=self.prefix,
+                                                                user_id=user.user_id),
+                                 title=user.title)
+
+    def get_all_principals(self, principal_id):
+        if not self.enabled:
+            return set()
+        if self.get_principal(principal_id) is not None:
+            return {principal_id}
+        return set()
+
+    def find_principals(self, query):
+        if not self.enabled:
+            raise StopIteration
+        # TODO: use inner text catalog for more efficient search?
+        if not query:
+            return None
+        query = query.lower()
+        for user in self.values():
+            if (query == user.user_id or
+                    query in (user.name or '').lower() or
+                    query in (user.email or '').lower()):
+                yield PrincipalInfo(id='{0}:{1}'.format(self.prefix, user.user_id),
+                                    title=user.title_with_source)
+
+    def get_search_results(self, data):
+        # TODO: use inner text catalog for more efficient search?
+        query = data.get('query')
+        if not query:
+            return ()
+        query = query.lower()
+        for user in self.values():
+            if (query == user.user_id or
+                    query in (user.name or '').lower() or
+                    query in (user.email or '').lower()):
+                yield user
+
+
+@provider(IVocabularyRegistry)
+class SocialUsersFolderVocabulary(SimpleVocabulary):
+    """'PyAMS users folders' vocabulary"""
+
+    def __init__(self, *args, **kwargs):
+        terms = []
+        manager = query_utility(ISecurityManager)
+        if manager is not None:
+            for name, plugin in manager.items():
+                if ISocialUsersFolderPlugin.providedBy(plugin):
+                    terms.append(SimpleTerm(name, title=plugin.title))
+        super(SocialUsersFolderVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS social users folders', SocialUsersFolderVocabulary)
+
+
+@subscriber(IAuthenticatedPrincipalEvent, plugin_selector='oauth')
+def handle_authenticated_principal(event):
+    """Handle authenticated social principal"""
+    manager = query_utility(ISecurityManager)
+    social_folder = manager.get(manager.social_users_folder)
+    if social_folder is not None:
+        infos = event.infos
+        if not (infos and
+                'provider_name' in infos and
+                'user' in infos):
+            return
+        user = infos['user']
+        principal_id = event.principal_id
+        if principal_id not in social_folder:
+            social_user = SocialUser()
+            check_request().registry.notify(ObjectCreatedEvent(social_user))
+            social_user.user_id = principal_id
+            social_user.provider_name = infos['provider_name']
+            social_user.username = user.username
+            social_user.name = user.name
+            social_user.first_name = user.first_name
+            social_user.last_name = user.last_name
+            social_user.nickname = user.nickname
+            social_user.email = user.email
+            social_user.timezone = str(user.timezone)
+            social_user.country = user.country
+            social_user.city = user.city
+            social_user.postal_code = user.postal_code
+            social_user.locale = user.locale
+            social_user.picture = user.picture
+            if isinstance(user.birth_date, datetime):
+                social_user.birth_date = user.birth_date
+            social_user.registration_date = datetime.utcnow()
+            social_folder[principal_id] = social_user
+
+
+#
+# OAuth providers configuration
+#
+
+@implementer(ISocialLoginProviderInfo)
+class SocialLoginProviderInfo(object):
+    """Social login provider info"""
+
+    name = FieldProperty(ISocialLoginProviderInfo['name'])
+    provider = None
+    icon_class = FieldProperty(ISocialLoginProviderInfo['icon_class'])
+    icon_filename = FieldProperty(ISocialLoginProviderInfo['icon_filename'])
+    scope = FieldProperty(ISocialLoginProviderInfo['scope'])
+
+    def __init__(self, name, provider, **kwargs):
+        self.name = name
+        self.provider = provider
+        for k, v in kwargs.items():
+            setattr(self, k, v)
+
+
+PROVIDERS_INFO = {'behance': SocialLoginProviderInfo(name=oauth2.Behance.__name__,
+                                                     provider=oauth2.Behance,
+                                                     icon_class='fa fa-fw fa-behance-square',
+                                                     icon_filename='behance.ico',
+                                                     scope=oauth2.Behance.user_info_scope),
+                  'bitbucket': SocialLoginProviderInfo(name=oauth1.Bitbucket.__name__,
+                                                       provider=oauth1.Bitbucket,
+                                                       icon_class='fa fa-fw fa-bitbucket',
+                                                       icon_filename='bitbucket.ico'),
+                  'bitly': SocialLoginProviderInfo(name=oauth2.Bitly.__name__,
+                                                   provider=oauth2.Bitly,
+                                                   icon_class='fa fa-fw fa-share-alt',
+                                                   icon_filename='bitly.ico',
+                                                   scope=oauth2.Bitly.user_info_scope),
+                  'cosm': SocialLoginProviderInfo(name=oauth2.Cosm.__name__,
+                                                  provider=oauth2.Cosm,
+                                                  icon_class='fa fa-fw fa-share-alt',
+                                                  icon_filename='cosm.ico',
+                                                  scope=oauth2.Cosm.user_info_scope),
+                  'devianart': SocialLoginProviderInfo(name=oauth2.DeviantART.__name__,
+                                                       provider=oauth2.DeviantART,
+                                                       icon_class='fa fa-fw fa-deviantart',
+                                                       icon_filename='deviantart.ico',
+                                                       scope=oauth2.DeviantART.user_info_scope),
+                  'facebook': SocialLoginProviderInfo(name=oauth2.Facebook.__name__,
+                                                      provider=oauth2.Facebook,
+                                                      icon_class='fa fa-fw fa-facebook-square',
+                                                      icon_filename='facebook.ico',
+                                                      scope=oauth2.Facebook.user_info_scope),
+                  'foursquare': SocialLoginProviderInfo(name=oauth2.Foursquare.__name__,
+                                                        provider=oauth2.Foursquare,
+                                                        icon_class='fa fa-fw fa-foursquare',
+                                                        icon_filename='foursquare.ico',
+                                                        scope=oauth2.Foursquare.user_info_scope),
+                  'flickr': SocialLoginProviderInfo(name=oauth1.Flickr.__name__,
+                                                    provider=oauth1.Flickr,
+                                                    icon_class='fa fa-fw fa-flickr',
+                                                    icon_filename='flickr.ico'),
+                  'github': SocialLoginProviderInfo(name=oauth2.GitHub.__name__,
+                                                    provider=oauth2.GitHub,
+                                                    icon_class='fa fa-fw fa-github',
+                                                    icon_filename='github.ico',
+                                                    scope=oauth2.GitHub.user_info_scope),
+                  'google': SocialLoginProviderInfo(name=oauth2.Google.__name__,
+                                                    provider=oauth2.Google,
+                                                    icon_class='fa fa-fw fa-google-plus',
+                                                    icon_filename='google.ico',
+                                                    scope=oauth2.Google.user_info_scope),
+                  'linkedin': SocialLoginProviderInfo(name=oauth2.LinkedIn.__name__,
+                                                      provider=oauth2.LinkedIn,
+                                                      icon_class='fa fa-fw fa-linkedin-square',
+                                                      icon_filename='linkedin.ico',
+                                                      scope=oauth2.LinkedIn.user_info_scope),
+                  'meetup': SocialLoginProviderInfo(name=oauth1.Meetup.__name__,
+                                                    provider=oauth1.Meetup,
+                                                    icon_class='fa fa-fw fa-share-alt',
+                                                    icon_filename='meetup.ico'),
+                  'paypal': SocialLoginProviderInfo(name=oauth2.PayPal.__name__,
+                                                    provider=oauth2.PayPal,
+                                                    icon_class='fa fa-fw fa-paypal',
+                                                    icon_filename='paypal.ico',
+                                                    scope=oauth2.PayPal.user_info_scope),
+                  'plurk': SocialLoginProviderInfo(name=oauth1.Plurk.__name__,
+                                                   provider=oauth1.Plurk,
+                                                   icon_class='fa fa-fw fa-share-alt',
+                                                   icon_filename='plurk.ico'),
+                  'reddit': SocialLoginProviderInfo(name=oauth2.Reddit.__name__,
+                                                    provider=oauth2.Reddit,
+                                                    icon_class='fa fa-fw fa-reddit',
+                                                    icon_filename='reddit.ico',
+                                                    scope=oauth2.Reddit.user_info_scope),
+                  'twitter': SocialLoginProviderInfo(name=oauth1.Twitter.__name__,
+                                                     provider=oauth1.Twitter,
+                                                     icon_class='fa fa-fw fa-twitter',
+                                                     icon_filename='twitter.ico'),
+                  'tumblr': SocialLoginProviderInfo(name=oauth1.Tumblr.__name__,
+                                                    provider=oauth1.Tumblr,
+                                                    icon_class='fa fa-fw fa-tumblr-square',
+                                                    icon_filename='tumblr.ico'),
+                  'ubuntuone': SocialLoginProviderInfo(name=oauth1.UbuntuOne.__name__,
+                                                       provider=oauth1.UbuntuOne,
+                                                       icon_class='fa fa-fw fa-share-alt',
+                                                       icon_filename='ubuntuone.ico'),
+                  'viadeo': SocialLoginProviderInfo(name=oauth2.Viadeo.__name__,
+                                                    provider=oauth2.Viadeo,
+                                                    icon_class='fa fa-fw fa-share-alt',
+                                                    icon_filename='viadeo.ico',
+                                                    scope=oauth2.Viadeo.user_info_scope),
+                  'vimeo': SocialLoginProviderInfo(name=oauth1.Vimeo.__name__,
+                                                   provider=oauth1.Vimeo,
+                                                   icon_class='fa fa-fw fa-vimeo-square',
+                                                   icon_filename='vimeo.ico'),
+                  'vk': SocialLoginProviderInfo(name=oauth2.VK.__name__,
+                                                provider=oauth2.VK,
+                                                icon_class='fa fa-fw fa-vk',
+                                                icon_filename='vk.ico',
+                                                scope=oauth2.VK.user_info_scope),
+                  'windowlive': SocialLoginProviderInfo(name=oauth2.WindowsLive.__name__,
+                                                        provider=oauth2.WindowsLive,
+                                                        icon_class='fa fa-fw fa-windows',
+                                                        icon_filename='windows_live.ico',
+                                                        scope=oauth2.WindowsLive.user_info_scope),
+                  'xero': SocialLoginProviderInfo(name=oauth1.Xero.__name__,
+                                                  provider=oauth1.Xero,
+                                                  icon_class='fa fa-fw fa-share-alt',
+                                                  icon_filename='xero.ico'),
+                  'xing': SocialLoginProviderInfo(name=oauth1.Xing.__name__,
+                                                  provider=oauth1.Xing,
+                                                  icon_class='fa fa-fw fa-xing',
+                                                  icon_filename='xing.ico'),
+                  'yahoo': SocialLoginProviderInfo(name=oauth1.Yahoo.__name__,
+                                                   provider=oauth1.Yahoo,
+                                                   icon_class='fa fa-fw fa-yahoo',
+                                                   icon_filename='yahoo.ico'),
+                  'yammer': SocialLoginProviderInfo(name=oauth2.Yammer.__name__,
+                                                    provider=oauth2.Yammer,
+                                                    icon_class='fa fa-fw fa-share-alt',
+                                                    icon_filename='yammer.ico',
+                                                    scope=oauth2.Yammer.user_info_scope),
+                  'yandex': SocialLoginProviderInfo(name=oauth2.Yandex.__name__,
+                                                    provider=oauth2.Yandex,
+                                                    icon_class='fa fa-fw fa-share-alt',
+                                                    icon_filename='yandex.ico',
+                                                    scope=oauth2.Yandex.user_info_scope)}
+
+
+def get_provider_info(provider_name):
+    """Get provider info matching given provider name"""
+    return PROVIDERS_INFO.get(provider_name)
+
+
+@provider(IVocabularyFactory)
+class OAuthProvidersVocabulary(SimpleVocabulary):
+    """OAuth providers vocabulary"""
+
+    def __init__(self, *args, **kwargs):
+        terms = []
+        for key, provider in PROVIDERS_INFO.items():
+            terms.append(SimpleTerm(key, title=provider.name))
+        terms.sort(key=lambda x: x.title)
+        super(OAuthProvidersVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS OAuth providers', OAuthProvidersVocabulary)
+
+
+@implementer(ISocialLoginConfiguration)
+class SocialLoginConfiguration(Folder):
+    """Social login configuration"""
+
+    def get_oauth_configuration(self):
+        result = {}
+        for provider in self.values():
+            provider_info = get_provider_info(provider.provider_name)
+            result[provider.provider_name] = {'id': provider.provider_id,
+                                              'class_': provider_info.provider,
+                                              'consumer_key': provider.consumer_key,
+                                              'consumer_secret': provider.consumer_secret,
+                                              'scope': provider_info.scope}
+        return result
+
+
+SOCIAL_LOGIN_CONFIGURATION_KEY = 'pyams_security.plugin.social'
+
+
+@adapter_config(context=ISecurityManager, provides=ISocialLoginConfiguration)
+def SocialLoginConfigurationAdapter(context):
+    """Social login configuration adapter"""
+    annotations = IAnnotations(context)
+    configuration = annotations.get(SOCIAL_LOGIN_CONFIGURATION_KEY)
+    if configuration is None:
+        configuration = annotations[SOCIAL_LOGIN_CONFIGURATION_KEY] = SocialLoginConfiguration()
+        check_request().registry.notify(ObjectCreatedEvent(configuration))
+        locate(configuration, context, '++social-configuration++')
+    return configuration
+
+
+@adapter_config(name='social-configuration', context=ISecurityManager, provides=ITraversable)
+class SecurityManagerSocialTraverser(ContextAdapter):
+    """++social-configuration++ namespace traverser"""
+
+    def traverse(self, name, furtherpath=None):
+        return ISocialLoginConfiguration(self.context)
+
+
+@implementer(ISocialLoginProviderConnection)
+class SocialLoginProviderConnection(Persistent):
+    """Social login provider connection"""
+
+    provider_name = FieldProperty(ISocialLoginProviderConnection['provider_name'])
+    provider_id = FieldProperty(ISocialLoginProviderConnection['provider_id'])
+    consumer_key = FieldProperty(ISocialLoginProviderConnection['consumer_key'])
+    consumer_secret = FieldProperty(ISocialLoginProviderConnection['consumer_secret'])
+
+    def get_configuration(self):
+        return get_provider_info(self.provider_name)
--- a/src/pyams_security/plugin/userfolder.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/plugin/userfolder.py	Mon Feb 23 17:55:05 2015 +0100
@@ -23,8 +23,7 @@
 from os import urandom
 
 # import interfaces
-from pyams_security.interfaces import ISecurityManager, IUsersFolderPlugin, IAdminAuthenticationPlugin, ILocalUser, \
-    IPrincipalInfo
+from pyams_security.interfaces import ISecurityManager, IUsersFolderPlugin, ILocalUser, IPrincipalInfo
 from zope.lifecycleevent.interfaces import IObjectAddedEvent
 from zope.password.interfaces import IPasswordManager
 from zope.schema.interfaces import IVocabularyRegistry
@@ -125,9 +124,9 @@
 class UsersFolder(Folder):
     """Local users folder"""
 
-    prefix = FieldProperty(IAdminAuthenticationPlugin['prefix'])
-    title = FieldProperty(IAdminAuthenticationPlugin['title'])
-    enabled = FieldProperty(IAdminAuthenticationPlugin['enabled'])
+    prefix = FieldProperty(IUsersFolderPlugin['prefix'])
+    title = FieldProperty(IUsersFolderPlugin['title'])
+    enabled = FieldProperty(IUsersFolderPlugin['enabled'])
 
     def authenticate(self, credentials, request):
         if not self.enabled:
@@ -151,8 +150,6 @@
         if not principal_id.startswith(self.prefix + ':'):
             return None
         prefix, login = principal_id.split(':', 1)
-        if prefix != self.prefix:
-            return None
         user = self.get(login)
         if user is not None:
             return PrincipalInfo(id='{0}:{1}'.format(self.prefix, user.login),
@@ -166,19 +163,22 @@
         return set()
 
     def find_principals(self, query):
-        # TODO: use users catalog for more efficient search?
+        if not self.enabled:
+            raise StopIteration
+        # TODO: use inner text catalog for more efficient search?
         if not query:
             return None
         query = query.lower()
         for user in self.values():
             if (query == user.login or
                     query in user.firstname.lower() or
-                    query in user.lastname.lower()):
+                    query in user.lastname.lower() or
+                    query in user.email.lower()):
                 yield PrincipalInfo(id='{0}:{1}'.format(self.prefix, user.login),
-                                    title=user.title)
+                                    title='{0} <{1}>'.format(user.title, user.email))
 
     def get_search_results(self, data):
-        # TODO: use users catalog for more efficient search?
+        # TODO: use inner text catalog for more efficient search?
         query = data.get('query')
         if not query:
             return ()
--- a/src/pyams_security/principal.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/principal.py	Mon Feb 23 17:55:05 2015 +0100
@@ -40,6 +40,9 @@
     def __eq__(self, other):
         return isinstance(other, PrincipalInfo) and (self.id == other.id)
 
+    def __hash__(self):
+        return hash(self.id)
+
 
 @implementer(IPrincipalInfo)
 class UnknownPrincipal(object):
@@ -66,7 +69,7 @@
 
     @property
     def title(self):
-        return '<MissingPrincipal: {0}>'.format(self.id)
+        return 'MissingPrincipal: {0}'.format(self.id)
 
     def __eq__(self, other):
         return isinstance(other, PrincipalInfo) and (self.id == other.id)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/property.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import IRole, IProtectedObject, IPrincipalInfo, IRoleProtectedObject
+from pyams_security.schema import IRoleField
+from zope.schema.interfaces import IField
+
+# import packages
+
+
+class RolePrincipalsFieldProperty(object):
+    """Custom field property used to handle role principals"""
+
+    def __init__(self, field, role_id=None, name=None, **args):
+        if not IField.providedBy(field):
+            raise ValueError("Provided field must implement IField interface")
+        if (role_id is None) and not IRoleField.providedBy(field):
+            raise ValueError("Provided field must implement IRoleField interface "
+                             "or you must provide a role ID")
+        if role_id is None:
+            role_id = field.role_id
+        elif IRole.providedBy(role_id):
+            role_id = role_id.id
+        if role_id is None:
+            raise ValueError("Can't get role ID")
+        if name is None:
+            name = field.__name__
+        self.__field = field
+        self.__name = name
+        self.__role_id = role_id
+
+    def __get__(self, instance, klass):
+        if instance is None:
+            return self
+        protection = IProtectedObject(instance, None)
+        if protection is None:
+            return set()
+        return protection.get_principals(self.__role_id)
+
+    def __set__(self, instance, value):
+        if value is None:
+            value = set()
+        elif isinstance(value, str):
+            value = set(value.split(','))
+        value = set(map(lambda x: x.id if IPrincipalInfo.providedBy(x) else x, value))
+        field = self.__field.bind(instance)
+        field.validate(value)
+        if field.readonly:
+            raise ValueError("Field {0} is readonly!".format(self.__name))
+        protection = IProtectedObject(instance, None)
+        if not IRoleProtectedObject.providedBy(protection):
+            raise ValueError("Can't use role properties on object not providing "
+                             "IRoleProtectedObject interface!")
+        old_principals = protection.get_principals(self.__role_id)
+        added = value - old_principals
+        removed = old_principals - value
+        for principal_id in added:
+            protection.grant_role(self.__role_id, principal_id)
+        for principal_id in removed:
+            protection.revoke_role(self.__role_id, principal_id)
Binary file src/pyams_security/resources/img/behance.ico has changed
Binary file src/pyams_security/resources/img/bitbucket.ico has changed
Binary file src/pyams_security/resources/img/bitly.ico has changed
Binary file src/pyams_security/resources/img/cosm.ico has changed
Binary file src/pyams_security/resources/img/deviantart.ico has changed
Binary file src/pyams_security/resources/img/facebook.ico has changed
Binary file src/pyams_security/resources/img/flickr.ico has changed
Binary file src/pyams_security/resources/img/foursquare.ico has changed
Binary file src/pyams_security/resources/img/github.ico has changed
Binary file src/pyams_security/resources/img/google.ico has changed
Binary file src/pyams_security/resources/img/linkedin.ico has changed
Binary file src/pyams_security/resources/img/meetup.ico has changed
Binary file src/pyams_security/resources/img/paypal.ico has changed
Binary file src/pyams_security/resources/img/plurk.ico has changed
Binary file src/pyams_security/resources/img/reddit.ico has changed
Binary file src/pyams_security/resources/img/tumblr.ico has changed
Binary file src/pyams_security/resources/img/twitter.ico has changed
Binary file src/pyams_security/resources/img/ubuntuone.ico has changed
Binary file src/pyams_security/resources/img/viadeo.ico has changed
Binary file src/pyams_security/resources/img/vimeo.ico has changed
Binary file src/pyams_security/resources/img/vk.ico has changed
Binary file src/pyams_security/resources/img/windows_live.ico has changed
Binary file src/pyams_security/resources/img/xero.ico has changed
Binary file src/pyams_security/resources/img/xing.ico has changed
Binary file src/pyams_security/resources/img/yahoo.ico has changed
Binary file src/pyams_security/resources/img/yammer.ico has changed
Binary file src/pyams_security/resources/img/yandex.ico has changed
--- a/src/pyams_security/resources/js/security.js	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/resources/js/security.js	Mon Feb 23 17:55:05 2015 +0100
@@ -27,17 +27,73 @@
 		},
 
 		/**
-		 * Ordered plug-ins callback
+		 * Plug-ins callback
 		 */
 		plugins: {
 
 			/**
+			 * Plug-in delete callback
+			 */
+			deletePlugin: function(link) {
+				return function() {
+					var link = $(this);
+					MyAMS.skin.bigBox({
+						title: MyAMS.i18n.WARNING,
+						content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; ' + MyAMS.i18n.DELETE_WARNING,
+						buttons: MyAMS.i18n.BTN_OK_CANCEL
+					}, function(button) {
+						if (button == MyAMS.i18n.BTN_OK) {
+							var location = link.parents('table').data('ams-location');
+							var tr = link.parents('tr');
+							var plugin_name = tr.data('ams-element-name');
+							MyAMS.ajax.post(location + '/delete-plugin.json', {'plugin_name': plugin_name}, function(result, status) {
+								if (result.status == 'success') {
+									link.parents('tr').remove();
+								}
+							});
+						}
+					});
+				}
+			},
+
+			/**
 			 * Change plug-ins names order
 			 */
 			changeOrder: function(table, names) {
 				var input = $('input[name="' + $(this).data('ams-input-name') + '"]', $(this));
 				input.val(names.join(';'));
 			}
+		},
+
+		/**
+		 * Social providers callbacks
+		 */
+		social: {
+
+			/**
+			 * Social login provider delete callback
+			 */
+			deleteProvider: function(link) {
+				return function() {
+					var link = $(this);
+					MyAMS.skin.bigBox({
+						title: MyAMS.i18n.WARNING,
+						content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; ' + MyAMS.i18n.DELETE_WARNING,
+						buttons: MyAMS.i18n.BTN_OK_CANCEL
+					}, function(button) {
+						if (button == MyAMS.i18n.BTN_OK) {
+							var location = link.parents('table').data('ams-location');
+							var tr = link.parents('tr');
+							var provider_name = tr.data('ams-element-name');
+							MyAMS.ajax.post(location + '/delete-provider.json', {'provider_name': provider_name}, function(result, status) {
+								if (result.status == 'success') {
+									link.parents('tr').remove();
+								}
+							});
+						}
+					});
+				}
+			}
 		}
 	};
 
--- a/src/pyams_security/resources/js/security.min.js	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/resources/js/security.min.js	Mon Feb 23 17:55:05 2015 +0100
@@ -1,1 +1,1 @@
-(function(a){window.PyAMS_security={init_social:function(b){MyAMS.ajax.check(window.authomatic,"/--static--/pyams_security/js/authomatic"+(MyAMS.devmode?".min.js":".js"),function(c){authomatic.setup({popupWidth:800,popupHeight:400,onLoginComplete:function(d){console.log(d);if(d.error){}else{if(d.user){alert(d.user)}}}});authomatic.popupInit()})},plugins:{changeOrder:function(c,d){var b=a('input[name="'+a(this).data("ams-input-name")+'"]',a(this));b.val(d.join(";"))}}}})(jQuery);
\ No newline at end of file
+(function(a){window.PyAMS_security={init_social:function(b){MyAMS.ajax.check(window.authomatic,"/--static--/pyams_security/js/authomatic"+(MyAMS.devmode?".min.js":".js"),function(c){authomatic.setup({popupWidth:800,popupHeight:400,onLoginComplete:function(d){console.log(d);if(d.error){}else{if(d.user){alert(d.user)}}}});authomatic.popupInit()})},plugins:{deletePlugin:function(b){return function(){var c=a(this);MyAMS.skin.bigBox({title:MyAMS.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; '+MyAMS.i18n.DELETE_WARNING,buttons:MyAMS.i18n.BTN_OK_CANCEL},function(f){if(f==MyAMS.i18n.BTN_OK){var d=c.parents("table").data("ams-location");var g=c.parents("tr");var e=g.data("ams-element-name");MyAMS.ajax.post(d+"/delete-plugin.json",{plugin_name:e},function(h,i){if(h.status=="success"){c.parents("tr").remove()}})}})}},changeOrder:function(c,d){var b=a('input[name="'+a(this).data("ams-input-name")+'"]',a(this));b.val(d.join(";"))}},social:{deleteProvider:function(b){return function(){var c=a(this);MyAMS.skin.bigBox({title:MyAMS.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; '+MyAMS.i18n.DELETE_WARNING,buttons:MyAMS.i18n.BTN_OK_CANCEL},function(f){if(f==MyAMS.i18n.BTN_OK){var d=c.parents("table").data("ams-location");var g=c.parents("tr");var e=g.data("ams-element-name");MyAMS.ajax.post(d+"/delete-provider.json",{provider_name:e},function(h,i){if(h.status=="success"){c.parents("tr").remove()}})}})}}}}})(jQuery);
\ No newline at end of file
--- a/src/pyams_security/role.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/role.py	Mon Feb 23 17:55:05 2015 +0100
@@ -12,14 +12,18 @@
 
 __docformat__ = 'restructuredtext'
 
+
 # import standard library
 
 # import interfaces
 from pyams_security.interfaces import IRole
+from zope.schema.interfaces import IVocabularyFactory
 
 # import packages
-from zope.interface import implementer
+from pyams_utils.request import check_request
+from zope.interface import implementer, provider
 from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import getVocabularyRegistry, SimpleVocabulary, SimpleTerm
 
 
 @implementer(IRole)
@@ -45,8 +49,40 @@
 
     Roles registry is not required.
     But only registered roles can be applied via default
-    ZMI features
+    ZMI features.
+
+    If a role is registered several times, previous registrations
+    will just be updated to add new permissions.
+    Title and description are not updated after first registration.
     """
+    registry = config.registry
     if not IRole.providedBy(role):
-        role = Role(role)
-    config.registry.registerUtility(role, IRole, name=role.id)
+        role_utility = registry.queryUtility(IRole, name=role.get('id'))
+        if role_utility is None:
+            # registering a new role
+            role_utility = Role(role)
+            registry.registerUtility(role_utility, IRole, name=role_utility.id)
+        else:
+            # new registration of a given role to add permissions
+            role_utility.permissions = (role_utility.permissions or set()) | \
+                                       (role.get('permissions') or set())
+    else:
+        registry.registerUtility(role, IRole, name=role.id)
+
+
+@provider(IVocabularyFactory)
+class RolesVocabulary(SimpleVocabulary):
+    """Roles vocabulary"""
+
+    interface = IRole
+
+    def __init__(self, *args, **kwargs):
+        request = check_request()
+        registry = request.registry
+        translate = request.localizer.translate
+        terms = [SimpleTerm(r.id, title=translate(r.title))
+                 for n, r in registry.getUtilitiesFor(self.interface)]
+        terms.sort(key=lambda x: x.title)
+        super(RolesVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS roles', RolesVocabulary)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/schema.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from zope.schema.interfaces import ITextLine, ISet
+
+# import packages
+from zope.interface import implementer, Interface
+from zope.schema import TextLine, Set
+
+
+class IRoleField(Interface):
+    """Base role field interface"""
+
+    role_id = TextLine(title="Matching role ID",
+                       required=False)
+
+
+class IPrincipal(ITextLine, IRoleField):
+    """Principal field interface"""
+
+
+@implementer(IPrincipal)
+class Principal(TextLine):
+    """Principal field"""
+
+    role_id = None
+
+    def __init__(self, **kwargs):
+        if 'role_id' in kwargs:
+            self.role_id = kwargs.pop('role_id')
+        super(Principal, self).__init__(**kwargs)
+
+
+class IPrincipalsSet(ISet, IRoleField):
+    """Principals set interface"""
+
+
+@implementer(IPrincipalsSet)
+class PrincipalsSet(Set):
+    """Principals set field"""
+
+    role_id = None
+
+    def __init__(self, **kwargs):
+        if 'role_id' in kwargs:
+            self.role_id = kwargs.pop('role_id')
+        super(PrincipalsSet, self).__init__(**kwargs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/security.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,188 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+import logging
+
+# import interfaces
+from pyams_security.interfaces import IProtectedObject, IRole, IPrincipalInfo, GrantedRoleEvent, RevokedRoleEvent, \
+    IDefaultProtectionPolicy, IRoleProtectedObject
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from persistent import Persistent
+from persistent.dict import PersistentDict
+from pyams_utils.adapter import adapter_config
+from pyams_utils.property import request_property
+from pyams_utils.registry import query_utility
+from pyams_utils.request import check_request
+from pyramid.location import lineage
+from pyramid.security import DENY_ALL, Everyone, Allow, ALL_PERMISSIONS, Authenticated
+from zope.interface import implementer
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location.location import locate
+from zope.schema.fieldproperty import FieldProperty
+
+
+@implementer(IRoleProtectedObject)
+class RoleProtectedObject(Persistent):
+    """Base class for object protected by roles"""
+
+    inherit_parent_security = FieldProperty(IRoleProtectedObject['inherit_parent_security'])
+    _everyone_permission = FieldProperty(IRoleProtectedObject['everyone_permission'])
+    _authenticated_permission = FieldProperty(IRoleProtectedObject['authenticated_permission'])
+    inherit_parent_roles = FieldProperty(IRoleProtectedObject['inherit_parent_roles'])
+
+    def __init__(self):
+        self._principals_by_role = PersistentDict()
+        self._roles_by_principal = PersistentDict()
+
+    @property
+    def everyone_permission(self):
+        permission = self._everyone_permission
+        if permission is None and self.inherit_parent_security:
+            for parent in lineage(self):
+                if parent is self:
+                    continue
+                protection = IProtectedObject(parent, None)
+                if protection is not None:
+                    permission = protection.everyone_permission
+                if permission is not None:
+                    break
+        return permission
+
+    @everyone_permission.setter
+    def everyone_permission(self, value):
+        self._everyone_permission = value
+
+    @property
+    def authenticated_permission(self):
+        permission = self._authenticated_permission
+        if permission is None and self.inherit_parent_security:
+            for parent in lineage(self):
+                if parent is self:
+                    continue
+                protection = IProtectedObject(parent, None)
+                if protection is not None:
+                    permission = protection.authenticated_permission
+                if permission is not None:
+                    break
+        return permission
+
+    @authenticated_permission.setter
+    def authenticated_permission(self, value):
+        self._authenticated_permission = value
+
+    def grant_role(self, role_id, principal_ids):
+        registry = check_request().registry
+        if IRole.providedBy(role_id):
+            role_id = role_id.id
+        if isinstance(principal_ids, str):
+            principal_ids = {principal_ids}
+        role_principals = self._principals_by_role.get(role_id) or set()
+        for principal_id in principal_ids:
+            if IPrincipalInfo.providedBy(principal_id):
+                principal_id = principal_id.id
+            if principal_id not in role_principals:
+                principal_roles = self._roles_by_principal.get(principal_id) or set()
+                role_principals.add(principal_id)
+                principal_roles.add(role_id)
+                self._roles_by_principal[principal_id] = principal_roles
+                self._principals_by_role[role_id] = role_principals
+                registry.notify(GrantedRoleEvent(self, role_id, principal_id))
+
+    def revoke_role(self, role_id, principal_ids):
+        registry = check_request().registry
+        if IRole.providedBy(role_id):
+            role_id = role_id.id
+        if isinstance(principal_ids, str):
+            principal_ids = {principal_ids}
+        role_principals = self._principals_by_role.get(role_id) or set()
+        for principal_id in principal_ids:
+            if IPrincipalInfo.providedBy(principal_id):
+                principal_id = principal_id.id
+            if principal_id in role_principals:
+                principal_roles = self._roles_by_principal.get(principal_id) or set()
+                role_principals.remove(principal_id)
+                principal_roles.remove(role_id)
+                self._roles_by_principal[principal_id] = principal_roles
+                self._principals_by_role[role_id] = role_principals
+                registry.notify(RevokedRoleEvent(self, role_id, principal_id))
+
+    def get_principals(self, role_id):
+        if IRole.providedBy(role_id):
+            role_id = role_id.id
+        return self._principals_by_role.get(role_id) or set()
+
+    def get_roles(self, principal_id):
+        if IPrincipalInfo.providedBy(principal_id):
+            principal_id = principal_id.id
+        return self._roles_by_principal.get(principal_id) or set()
+
+    def get_permissions(self, principal_id):
+        registry = check_request().registry
+        result = set()
+        for role_id in self.get_roles(principal_id):
+            role = registry.queryUtility(IRole, role_id)
+            result |= role.permissions or set()
+        return result
+
+    @request_property(key=None)
+    def __acl__(self):
+        # always grant all permissions to system manager
+        result = [(Allow, 'system:admin', ALL_PERMISSIONS)]
+        # grant permission to everyone and authenticated
+        if self.everyone_permission:
+            result.append((Allow, Everyone, self.everyone_permission))
+        if self.authenticated_permission:
+            result.append((Allow, Authenticated, self.authenticated_permission))
+        # grant access to all roles permissions
+        for role_id in self._principals_by_role.keys():
+            role = query_utility(IRole, role_id)
+            if role is not None:
+                result.append((Allow, 'role:{0}'.format(role_id), role.permissions))
+        # stop inheritance if required
+        if not self.inherit_parent_security:
+            result.append(DENY_ALL)
+        logging.getLogger('PyAMS').debug(result)
+        print(result)
+        return result
+
+
+ROLES_ANNOTATIONS_KEY = 'pyams_security.roles'
+
+
+@adapter_config(context=IDefaultProtectionPolicy, provides=IRoleProtectedObject)
+def ProtectedObjectFactory(context):
+    """Default protected object factory"""
+    annotations = IAnnotations(context)
+    protection = annotations.get(ROLES_ANNOTATIONS_KEY)
+    if protection is None:
+        protection = annotations[ROLES_ANNOTATIONS_KEY] = RoleProtectedObject()
+        check_request().registry.notify(ObjectCreatedEvent(protection))
+        locate(protection, context)
+    return protection
+
+
+class ProtectedObject(object):
+    """Base protected object class"""
+
+    def __acl__(self):
+        protected = IProtectedObject(self, None)
+        if protected is not None:
+            acl = protected.__acl__()
+            if callable(acl):
+                acl = acl(protected)
+            return acl
+        return []
--- a/src/pyams_security/utility.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/utility.py	Mon Feb 23 17:55:05 2015 +0100
@@ -17,15 +17,17 @@
 
 # import interfaces
 from pyams_security.interfaces import ISecurityManager, ICredentialsPlugin, IAuthenticationPlugin, \
-    IDirectoryPlugin, AuthenticatedPrincipalEvent
+    IDirectoryPlugin, AuthenticatedPrincipalEvent, IProtectedObject
 from pyramid.interfaces import IAuthenticationPolicy
 
 # import packages
 from pyams_security.principal import UnknownPrincipal, MissingPrincipal
 from pyams_utils.registry import query_utility
 from pyams_utils.request import check_request
+from pyams_utils.wsgi import wsgi_environ_cache
 from pyramid.authentication import AuthTktCookieHelper
 from pyramid.decorator import reify
+from pyramid.location import lineage
 from pyramid.security import Everyone, Authenticated
 from zope.container.folder import Folder
 from zope.interface import implementer
@@ -37,6 +39,7 @@
     """Security manager utility"""
 
     enable_social_login = FieldProperty(ISecurityManager['enable_social_login'])
+    social_users_folder = FieldProperty(ISecurityManager['social_users_folder'])
     authomatic_secret = FieldProperty(ISecurityManager['authomatic_secret'])
     social_login_use_popup = FieldProperty(ISecurityManager['social_login_use_popup'])
     open_registration = FieldProperty(ISecurityManager['open_registration'])
@@ -61,9 +64,9 @@
     def __delitem__(self, key):
         super(SecurityManager, self).__delitem__(key)
         if key in self.authentication_plugins_names:
-            del self.authentication_plugins_names[self.authentication_plugins_names.index(key)]
+            self.authentication_plugins_names = tuple(filter(lambda x: x != key, self.authentication_plugins_names))
         if key in self.directory_plugins_names:
-            del self.directory_plugins_names[self.directory_plugins_names.index(key)]
+            self.directory_plugins_names = tuple(filter(lambda x: x != key, self.directory_plugins_names))
 
     def get_plugin(self, name):
         if name in self.credentials_plugins_names:
@@ -115,10 +118,23 @@
             if principal is not None:
                 return principal.id
 
-    def effective_principals(self, principal_id):
+    def effective_principals(self, principal_id, request=None):
         principals = set()
         for plugin in self.get_directory_plugins():
             principals |= set(plugin.get_all_principals(principal_id))
+        if request is None:
+            request = check_request()
+        # add context roles granted to principal
+        context = request.context
+        if context is not None:
+            for parent in lineage(context):
+                protection = IProtectedObject(parent, None)
+                if protection is not None:
+                    for principal_id in principals.copy():
+                        principals |= set(map(lambda x: 'role:{0}'.format(x),
+                                              protection.get_roles(principal_id)))
+                    if not protection.inherit_parent_roles:
+                        break
         return principals
 
     # IDirectoryPlugin interface methods
@@ -134,7 +150,7 @@
     def find_principals(self, query):
         principals = set()
         for plugin in self.get_directory_plugins():
-            principals |= set(plugin.find_principals(query) or set())
+            principals |= set(plugin.find_principals(query))
         return sorted(principals, key=lambda x: x.title)
 
 
@@ -153,7 +169,7 @@
     """
 
     def __init__(self, secret,
-                 credentials=('http', 'session'),
+                 credentials=('http', ),
                  cookie_name='auth_ticket',
                  secure=False,
                  include_ip=False,
@@ -189,6 +205,7 @@
     def _get_security_manager(self, request):
         return query_utility(ISecurityManager)
 
+    @wsgi_environ_cache('pyams_security.unauthenticated_userid')
     def unauthenticated_userid(self, request):
         result = self.cookie.identify(request)
         if result:
@@ -199,6 +216,7 @@
                 if credentials is not None:
                     return credentials.id
 
+    @wsgi_environ_cache('pyams_security.authenticated_userid')
     def authenticated_userid(self, request):
         principal_id = self.unauthenticated_userid(request)
         if principal_id:
@@ -207,10 +225,12 @@
         if manager is not None:
             return manager.authenticated_userid(request)
 
+    @wsgi_environ_cache('pyams_security.effective_principals')
     def effective_principals(self, request):
         principals = {Everyone}
         principal_id = self.unauthenticated_userid(request)
         if principal_id:
+            # get authenticated user principals
             principals.add(Authenticated)
             principals.add(principal_id)
             manager = self._get_security_manager(request)
--- a/src/pyams_security/views/oauth.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/views/oauth.py	Mon Feb 23 17:55:05 2015 +0100
@@ -19,12 +19,12 @@
 # import interfaces
 from pyams_form.interfaces.form import IWidgetsSuffixViewletsManager
 from pyams_skin.layer import IPyAMSLayer
-from pyams_security.interfaces import AuthenticatedPrincipalEvent, LOGIN_REFERER_KEY, ISecurityManager, ILoginView
+from pyams_security.interfaces import AuthenticatedPrincipalEvent, LOGIN_REFERER_KEY, ISecurityManager, ILoginView, \
+    ISocialLoginConfiguration
 
 # import packages
 from authomatic.adapters import WebObAdapter
 from authomatic.core import Authomatic
-from authomatic.providers import oauth1, oauth2
 from pyams_template.template import template_config
 from pyams_utils.registry import query_utility
 from pyams_viewlet.viewlet import Viewlet, viewlet_config
@@ -55,41 +55,12 @@
         manager = query_utility(ISecurityManager)
         return manager.social_login_use_popup
 
-
-CONFIG = {
-
-    'twitter': {
-        'id': 10,
-        # Provider class
-        'class_': oauth1.Twitter,
-
-        # Twitter is an AuthorizationProvider so we need to set several other properties too:
-        'consumer_key': 'F41qIog95eVs07RupYccokWbk',
-        'consumer_secret': 'yrEYMT3VnkT4yvCsXvaKDWok5gehU6fjz38e2FTphE8BXaapUA',
-    },
-
-    'facebook': {
-        'id': 20,
-        # Provider class
-        'class_': oauth2.Facebook,
-
-        # Facebook is an AuthorizationProvider too.
-        'consumer_key': '417712258385794',
-        'consumer_secret': '0a2bfc72bdc05caff800b8e46fe8ca64',
-
-        # But it is also an OAuth 2.0 provider and it needs scope.
-        'scope': ['email'],
-    },
-
-    'google': {
-        'id': 30,
-        # Provider class
-        'class_': oauth2.Google,
-        'consumer_key': '203791088227-a2nuqdo2t74ebv1n0s8houb4jfmjtp1t.apps.googleusercontent.com',
-        'consumer_secret': 'gsATqmPQASMtC60OACMcOH0i',
-        'scope': ['email']
-    }
-}
+    @property
+    def providers(self):
+        manager = query_utility(ISecurityManager)
+        configuration = ISocialLoginConfiguration(manager)
+        return sorted(configuration.values(),
+                      key=lambda x: x.provider_id)
 
 
 @view_config(route_name='login')
@@ -102,11 +73,11 @@
     # store referer
     session = request.session
     if LOGIN_REFERER_KEY not in session:
-        referer = request.referer
-        session[LOGIN_REFERER_KEY] = referer
+        session[LOGIN_REFERER_KEY] = request.referer
     # init authomatic
     provider_name = request.matchdict.get('provider_name')
-    authomatic = Authomatic(config=CONFIG, secret=manager.authomatic_secret, logging_level=WARNING)
+    configuration = ISocialLoginConfiguration(manager).get_oauth_configuration()
+    authomatic = Authomatic(config=configuration, secret=manager.authomatic_secret, logging_level=WARNING)
     # perform login
     response = Response()
     result = authomatic.login(WebObAdapter(request, response), provider_name)
@@ -116,10 +87,15 @@
         elif result.user:
             if not (result.user.id and result.user.name):
                 result.user.update()
-            principal_id = 'oauth:{0}.{1}'.format(provider_name, result.user.id)
-            request.registry.notify(AuthenticatedPrincipalEvent('oauth',
-                                                                principal_id=principal_id,
+            social_folder = manager.get(manager.social_users_folder)
+            user_id = '{provider_name}.{user_id}'.format(provider_name=provider_name,
+                                                         user_id=result.user.id)
+            request.registry.notify(AuthenticatedPrincipalEvent(plugin='oauth',
+                                                                principal_id=user_id,
+                                                                provider_name=provider_name,
                                                                 user=result.user))
+            principal_id = '{prefix}:{user_id}'.format(prefix=social_folder.prefix,
+                                                       user_id=user_id)
             headers = remember(request, principal_id)
             response.headerlist.extend(headers)
         if manager.social_login_use_popup:
--- a/src/pyams_security/views/templates/social-login.pt	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/views/templates/social-login.pt	Mon Feb 23 17:55:05 2015 +0100
@@ -4,7 +4,14 @@
 		  data-ams-plugin-pyams_security-src="/--static--/pyams_security/js/security.js"
 		  data-ams-plugin-pyams_security-callback="PyAMS_security.init_social"></span>
 	<span>Login with: </span>
-	<a class="authomatic" href="/login/facebook">Facebook</a>
-	<a class="authomatic" href="/login/twitter">Twitter</a>
-	<a class="authomatic" href="/login/google">Google</a>
+	<tal:loop repeat="provider view.providers">
+		<a class="authomatic hint"
+		   data-ams-hint-gravity="n"
+		   tal:define="config provider.get_configuration()"
+		   tal:attributes="title config.name;
+						   href string:/login/${provider.provider_name}">
+			<img width="16" height="16"
+				 tal:attributes="src string:/--static--/pyams_security/img/${config.icon_filename}" />
+		</a>
+	</tal:loop>
 </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/views/utility.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import ISecurityManager
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_utils.registry import query_utility
+from pyramid.view import view_config
+
+
+@view_config(name='find-principals.json', request_type=IPyAMSLayer,
+             permission='system.view', renderer='json', xhr=True)
+def find_principals(request):
+    """Find all principals matching given query"""
+    query = request.params.get('query')
+    if not query:
+        return []
+    manager = query_utility(ISecurityManager)
+    return [{'id': principal.id,
+             'text': principal.title} for principal in manager.find_principals(query)]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/__init__.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,114 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import json
+
+# import interfaces
+from pyams_form.interfaces.form import IFormLayer
+from pyams_security.interfaces import ISecurityManager
+from pyams_security.schema import IPrincipal, IPrincipalsSet
+from pyams_security.widget.interfaces import IPrincipalWidget, IPrincipalsSetWidget
+from z3c.form.interfaces import IDataConverter, IFieldWidget
+
+# import packages
+from pyams_form.widget import widgettemplate_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import query_utility
+from z3c.form.browser.widget import HTMLInputWidget
+from z3c.form.converter import BaseDataConverter
+from z3c.form.widget import Widget, FieldWidget
+from zope.interface import implementer_only
+
+
+#
+# Principal widget
+#
+
+@adapter_config(context=(IPrincipal, IPrincipalWidget), provides=IDataConverter)
+class PrincipalDataConverter(BaseDataConverter):
+    """Principal data converter"""
+
+    def toWidgetValue(self, value):
+        # Principal widget is waiting for a PrincipalInfo object
+        if not value:
+            return None
+        manager = query_utility(ISecurityManager)
+        return manager.get_principal(value)
+
+    def toFieldValue(self, value):
+        # Principal widget only returns selected principal ID
+        return value
+
+
+@widgettemplate_config(mode='input', template='templates/principal-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/principal-display.pt', layer=IFormLayer)
+@implementer_only(IPrincipalWidget)
+class PrincipalWidget(HTMLInputWidget, Widget):
+    """Principal widget"""
+
+    @property
+    def value_map(self):
+        if not self.value:
+            return ''
+        return json.dumps({self.value.id: self.value.title})
+
+
+@adapter_config(context=(IPrincipal, IFormLayer), provides=IFieldWidget)
+def PrincipalFieldWidget(field, request):
+    """Principal field widget factory"""
+    return FieldWidget(field, PrincipalWidget(request))
+
+
+#
+# Principals set widget
+#
+
+@adapter_config(context=(IPrincipalsSet, IPrincipalsSetWidget), provides=IDataConverter)
+class PrincipalsSetDataConverter(BaseDataConverter):
+    """Principals set data converter"""
+
+    def toWidgetValue(self, value):
+        # Principals set widget is waiting for a set of PrincipalInfo object
+        if not value:
+            return ()
+        manager = query_utility(ISecurityManager)
+        return sorted((manager.get_principal(principal_id) for principal_id in value),
+                      key=lambda x: x.title)
+
+    def toFieldValue(self, value):
+        # Principals set widget only returns selected principal IDs in a single input
+        if not value:
+            return set()
+        return set(value.split(','))
+
+
+@widgettemplate_config(mode='input', template='templates/principals-set-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/principals-set-display.pt', layer=IFormLayer)
+@implementer_only(IPrincipalsSetWidget)
+class PrincipalsSetWidget(HTMLInputWidget, Widget):
+    """Principals set widget"""
+
+    @property
+    def values_map(self):
+        result = {}
+        [result.update({value.id: value.title}) for value in self.value]
+        return json.dumps(result)
+
+
+@adapter_config(context=(IPrincipalsSet, IFormLayer), provides=IFieldWidget)
+def PrincipalsSetFieldWidget(field, request):
+    """Principals set field widget factory"""
+    return FieldWidget(field, PrincipalsSetWidget(request))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/interfaces.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from z3c.form.interfaces import IWidget
+
+# import packages
+
+
+class IPrincipalWidget(IWidget):
+    """Principal widget"""
+
+
+class IPrincipalsSetWidget(IWidget):
+    """Principals set widget"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/principal-display.pt	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,26 @@
+<input type="hidden" autocomplete="off" readonly
+	tal:attributes="id view/id;
+					name view/name;
+					class string:select2 ${view/klass};
+					style view/style;
+					title view/title;
+					value view/value/id | nothing;
+					lang view/lang;
+					onclick view/onclick;
+					ondblclick view/ondblclick;
+					onmousedown view/onmousedown;
+					onmouseup view/onmouseup;
+					onmouseover view/onmouseover;
+					onmousemove view/onmousemove;
+					onmouseout view/onmouseout;
+					onkeypress view/onkeypress;
+					onkeydown view/onkeydown;
+					onkeyup view/onkeyup;
+					disabled view/disabled;
+					tabindex view/tabindex;
+					onfocus view/onfocus;
+					onblur view/onblur;
+					onchange view/onchange;
+					accesskey view/accesskey;
+					onselect view/onselect;
+					data-ams-select2-values view/value_map;" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/principal-input.pt	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,41 @@
+<label class="input bordered with-icon"
+	   tal:omit-tag="view/required" i18n:domain="pyams_security">
+	<i class="icon-append fa fa-trash-o hint opaque"
+		title="Clear selected values" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
+		tal:attributes="data-ams-select2-target view/name"></i>
+	<div class="select2-parent"
+		 tal:omit-tag="view/required">
+		<input type="hidden" autocomplete="off"
+			data-ams-select2-minimum-input-length="2"
+			data-ams-select2-allow-clear="true"
+			data-ams-select2-query-url="find-principals.json"
+			tal:attributes="id view/id;
+							name view/name;
+							class string:select2 ${view/klass};
+							style view/style;
+							title view/title;
+							value view/value/id | nothing;
+							lang view/lang;
+							onclick view/onclick;
+							ondblclick view/ondblclick;
+							onmousedown view/onmousedown;
+							onmouseup view/onmouseup;
+							onmouseover view/onmouseover;
+							onmousemove view/onmousemove;
+							onmouseout view/onmouseout;
+							onkeypress view/onkeypress;
+							onkeydown view/onkeydown;
+							onkeyup view/onkeyup;
+							disabled view/disabled;
+							tabindex view/tabindex;
+							onfocus view/onfocus;
+							onblur view/onblur;
+							onchange view/onchange;
+							readonly view/readonly;
+							accesskey view/accesskey;
+							onselect view/onselect;
+							data-ams-select2-values view/value_map;" />
+	</div>
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/principals-set-display.pt	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,27 @@
+<input type="hidden" autocomplete="off" readonly
+	data-ams-select2-multiple="true"
+	tal:attributes="id view/id;
+					name view/name;
+					class string:select2 ${view/klass};
+					style view/style;
+					title view/title;
+					value python:','.join((value.id for value in view.value));
+					lang view/lang;
+					onclick view/onclick;
+					ondblclick view/ondblclick;
+					onmousedown view/onmousedown;
+					onmouseup view/onmouseup;
+					onmouseover view/onmouseover;
+					onmousemove view/onmousemove;
+					onmouseout view/onmouseout;
+					onkeypress view/onkeypress;
+					onkeydown view/onkeydown;
+					onkeyup view/onkeyup;
+					disabled view/disabled;
+					tabindex view/tabindex;
+					onfocus view/onfocus;
+					onblur view/onblur;
+					onchange view/onchange;
+					accesskey view/accesskey;
+					onselect view/onselect;
+					data-ams-select2-values view/values_map;" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/principals-set-input.pt	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,42 @@
+<label class="input bordered with-icon"
+	   tal:omit-tag="view/required" i18n:domain="pyams_security">
+	<i class="icon-append fa fa-trash-o hint opaque"
+		title="Clear selected values" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
+		tal:attributes="data-ams-select2-target view/name"></i>
+	<div class="select2-parent"
+		 tal:omit-tag="view/required">
+		<input type="hidden" autocomplete="off"
+			data-ams-select2-minimum-input-length="2"
+			data-ams-select2-allow-clear="true"
+			data-ams-select2-multiple="true"
+			data-ams-select2-query-url="find-principals.json"
+			tal:attributes="id view/id;
+							name view/name;
+							class string:select2 ${view/klass};
+							style view/style;
+							title view/title;
+							value python:','.join((value.id for value in view.value));
+							lang view/lang;
+							onclick view/onclick;
+							ondblclick view/ondblclick;
+							onmousedown view/onmousedown;
+							onmouseup view/onmouseup;
+							onmouseover view/onmouseover;
+							onmousemove view/onmousemove;
+							onmouseout view/onmouseout;
+							onkeypress view/onkeypress;
+							onkeydown view/onkeydown;
+							onkeyup view/onkeyup;
+							disabled view/disabled;
+							tabindex view/tabindex;
+							onfocus view/onfocus;
+							onblur view/onblur;
+							onchange view/onchange;
+							readonly view/readonly;
+							accesskey view/accesskey;
+							onselect view/onselect;
+							data-ams-select2-values view/values_map;" />
+	</div>
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/zmi/plugin/group.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,298 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import IGroupsFolderPlugin, ISecurityManager, ILocalGroup
+from pyams_security.zmi.interfaces import ISecurityManagerToolbarAddingMenu
+from pyams_skin.interfaces import IPageHeader, IInnerPage
+from pyams_skin.interfaces.viewlet import IToolbarViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import DISPLAY_MODE, IDataExtractedEvent
+from z3c.table.interfaces import IColumn, IValues
+from zope.component.interfaces import ISite
+
+# import packages
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_security.plugin.group import GroupsFolder, Group
+from pyams_security.zmi.utility import SecurityManagerPluginsTable
+from pyams_skin.container import ContainerView
+from pyams_skin.table import BaseTable, I18nColumn
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem, ToolbarAction
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.registry import query_utility
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.view import AdminView
+from pyramid.events import subscriber
+from pyramid.view import view_config
+from z3c.form import field
+from z3c.table.column import GetAttrColumn
+from zope.interface import implementer, Invalid
+
+from pyams_security import _
+
+
+#
+# Groups folder views
+#
+
+@viewlet_config(name='add-groups-folder.menu', context=ISite, layer=IAdminLayer,
+                view=SecurityManagerPluginsTable, manager=ISecurityManagerToolbarAddingMenu,
+                permission='system.manage', weight=20)
+class GroupsFolderAddMenu(ToolbarMenuItem):
+    """Local groups folder add menu"""
+
+    label = _("Add local groups folder...")
+    label_css_class = 'fa fa-fw fa-users'
+    url = 'add-groups-folder.html'
+    modal_target = True
+
+
+@pagelet_config(name='add-groups-folder.html', context=ISite, layer=IPyAMSLayer,
+                permission='system.manage')
+class GroupsFolderAddForm(AdminDialogAddForm):
+    """Groups folder plug-in add form"""
+
+    title = _("System security manager")
+    legend = _("Add local groups folder plug-in")
+    icon_css_class = 'fa fa-fw fa-users'
+
+    fields = field.Fields(IGroupsFolderPlugin).omit('__name__', '__parent__')
+    ajax_handler = 'add-groups-folder.json'
+    edit_permission = None
+
+    def create(self, data):
+        return GroupsFolder()
+
+    def add(self, plugin):
+        context = query_utility(ISecurityManager)
+        context[plugin.prefix] = plugin
+
+    def nextURL(self):
+        return absolute_url(self.context, self.request, 'security-manager.html')
+
+
+@view_config(name='add-groups-folder.json', context=ISite, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class GroupsFolderAJAXAddForm(AJAXAddForm, GroupsFolderAddForm):
+    """Groups folder plug-in add form, AJAX handler"""
+
+
+@pagelet_config(name='properties.html', context=IGroupsFolderPlugin, layer=IPyAMSLayer,
+                permission='system.view')
+class GroupsFolderEditForm(AdminDialogEditForm):
+    """Groups folder plug-in edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit local groups folder plug-in properties")
+    icon_css_class = 'fa fa-fw fa-users'
+
+    fields = field.Fields(IGroupsFolderPlugin).omit('__name__', '__parent__')
+    ajax_handler = 'properties.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(GroupsFolderEditForm, self).updateWidgets()
+        self.widgets['prefix'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=IGroupsFolderPlugin, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class GroupsFolderAJAXEditForm(AJAXEditForm, GroupsFolderEditForm):
+    """Groups folder plug-in edit form, AJAX handler"""
+
+
+#
+# Groups folder search views
+#
+
+class GroupsFolderContentsTable(BaseTable):
+    """Groups folder contents table"""
+
+    id = 'groups_folder_table'
+    title = _("Local groups")
+    cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight datatable'}
+
+
+@adapter_config(context=(IGroupsFolderPlugin, IAdminLayer, GroupsFolderContentsTable), provides=IValues)
+class GroupsFolderContentsValuesAdapter(ContextRequestViewAdapter):
+    """Groups folder table values adapter"""
+
+    @property
+    def values(self):
+        return list(self.context.values())
+
+
+@adapter_config(name='name', context=(IGroupsFolderPlugin, IAdminLayer, GroupsFolderContentsTable),
+                provides=IColumn)
+class NameColumn(I18nColumn, GetAttrColumn):
+    """Group name column"""
+
+    _header = _("Name")
+    attrName = 'title'
+    weight = 10
+
+
+@adapter_config(name='description', context=(IGroupsFolderPlugin, IAdminLayer, GroupsFolderContentsTable),
+                provides=IColumn)
+class DescriptionColumn(I18nColumn, GetAttrColumn):
+    """Users registration date column"""
+
+    _header = _("Description")
+    attrName = 'description'
+    weight = 20
+
+    def getValue(self, obj):
+        return super(DescriptionColumn, self).getValue(obj) or ''
+
+
+@pagelet_config(name='search.html', context=IGroupsFolderPlugin, layer=IPyAMSLayer, permission='system.view')
+@implementer(IInnerPage)
+class GroupsFolderSearchView(AdminView, ContainerView):
+    """Groups folder search view"""
+
+    table_class = GroupsFolderContentsTable
+
+    def __init__(self, context, request):
+        super(GroupsFolderSearchView, self).__init__(context, request)
+
+
+@adapter_config(context=(IGroupsFolderPlugin, IAdminLayer, GroupsFolderSearchView), provides=IPageHeader)
+class GroupsFolderSearchViewHeaderAdapter(ContextRequestViewAdapter):
+    """Groups folder search view header adapter"""
+
+    back_url = '#security-manager.html'
+    icon_class = 'fa fa-fw fa-users'
+
+    @property
+    def title(self):
+        return self.context.title
+
+    subtitle = _("Groups list")
+
+
+#
+# Groups views
+#
+
+@viewlet_config(name='groups-folder.toolbar.adding', context=IGroupsFolderPlugin,
+                view=GroupsFolderContentsTable, layer=IAdminLayer,
+                manager=IToolbarViewletManager, permission='system.manage')
+class LocalGroupAddAction(ToolbarAction):
+    """Groups folder adding action"""
+
+    label = _("Add group")
+    url = 'add-group.html'
+    modal_target = True
+
+
+@pagelet_config(name='add-group.html', context=IGroupsFolderPlugin, layer=IPyAMSLayer, permission='system.manage')
+class LocalGroupAddForm(AdminDialogAddForm):
+    """Local group add form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Add new local group")
+    icon_css_class = 'fa fa-fw fa-users'
+    label_css_class = 'control-label col-md-4'
+    input_css_class = 'col-md-8'
+
+    fields = field.Fields(ILocalGroup).omit('__parent__', '__name__')
+
+    ajax_handler = 'add-group.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(LocalGroupAddForm, self).updateWidgets()
+        self.widgets['description'].label_css_class = 'textarea'
+
+    def create(self, data):
+        return Group()
+
+    def update_content(self, group, data):
+        group.group_id = data.get('group_id')
+        group.title = data.get('title')
+        group.description = data.get('description')
+        group.principals = data.get('principals')
+
+    def add(self, group):
+        self.context[group.group_id] = group
+
+
+@subscriber(IDataExtractedEvent, form_selector=LocalGroupAddForm)
+def handle_new_user_data_extraction(event):
+    """Handle new group form data extraction"""
+    data = event.data
+    folder = event.form.context
+    if not folder.check_group_id(data.get('group_id')):
+        event.form.widgets.errors += (Invalid(_("Specified group ID can't be used!")),)
+
+
+@view_config(name='add-group.json', context=IGroupsFolderPlugin, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class LocalGroupAJAXAddForm(AJAXAddForm, LocalGroupAddForm):
+    """Local group add form, AJAX view"""
+
+    def get_ajax_output(self, changes):
+        translate = self.request.localizer.translate
+        return {'status': 'reload',
+                'location': absolute_url(self.context, self.request, 'search.html'),
+                'message': translate(_("Group was created successfully"))}
+
+
+@pagelet_config(name='properties.html', context=ILocalGroup, layer=IPyAMSLayer, permission='system.view')
+class LocalGroupEditForm(AdminDialogEditForm):
+    """Local group edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit group properties")
+    icon_css_class = 'fa fa-fw fa-users'
+    label_css_class = 'control-label col-md-4'
+    input_css_class = 'col-md-8'
+
+    fields = field.Fields(ILocalGroup).omit('__parent__', '__name__')
+
+    ajax_handler = 'properties.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(LocalGroupEditForm, self).updateWidgets()
+        self.widgets['description'].label_css_class = 'textarea'
+
+
+@view_config(name='properties.json', context=ILocalGroup, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class LocalGroupAJAXEditForm(AJAXEditForm, LocalGroupEditForm):
+    """Local group edit form, AJAX view"""
+
+    def get_ajax_output(self, changes):
+        if 'title' in changes.get(ILocalGroup, ()):
+            return {'status': 'reload',
+                    'location': absolute_url(self.context.__parent__, self.request, 'search.html')}
+        else:
+            return super(LocalGroupAJAXEditForm, self).get_ajax_output(changes)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/zmi/plugin/social.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,479 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import ISecurityManager, ISocialUsersFolderPlugin, ISocialUser, \
+    ISocialLoginConfiguration, ISocialLoginProviderConnection
+from pyams_security.zmi.interfaces import ISecurityManagerToolbarAddingMenu, ISecurityManagerMenu
+from pyams_skin.interfaces import IPageHeader, IInnerPage
+from pyams_skin.interfaces.viewlet import IToolbarViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import DISPLAY_MODE, IDataExtractedEvent
+from z3c.table.interfaces import IColumn, IValues
+from zope.component.interfaces import ISite
+from zope.dublincore.interfaces import IZopeDublinCore
+
+# import packages
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.search import SearchView, SearchResultsView
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_security.plugin.social import SocialUsersFolder, SocialLoginProviderConnection, get_provider_info
+from pyams_security.zmi.utility import SecurityManagerPluginsTable
+from pyams_skin.container import ContainerView
+from pyams_skin.skin import apply_skin
+from pyams_skin.table import I18nColumn, BaseTable, ActionColumn
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem, ToolbarAction
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.date import format_datetime
+from pyams_utils.registry import query_utility
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.view import AdminView
+from pyramid.events import subscriber
+from pyramid.view import view_config
+from z3c.form import field
+from z3c.table.column import GetAttrColumn
+from zope.interface import implementer, Interface, Invalid
+
+from pyams_security import _
+
+
+#
+# Users folder views
+#
+
+@viewlet_config(name='add-social-users-folder.menu', context=ISite, layer=IAdminLayer,
+                view=SecurityManagerPluginsTable, manager=ISecurityManagerToolbarAddingMenu,
+                permission='system.manage', weight=50)
+class SocialUsersFolderAddMenu(ToolbarMenuItem):
+    """Social users folder add menu"""
+
+    label = _("Add social users folder...")
+    label_css_class = 'fa fa-fw fa-share-alt'
+    url = 'add-social-users-folder.html'
+    modal_target = True
+
+
+@pagelet_config(name='add-social-users-folder.html', context=ISite, layer=IPyAMSLayer,
+                permission='system.manage')
+class SocialUsersFolderAddForm(AdminDialogAddForm):
+    """Social users folder plug-in add form"""
+
+    title = _("System security manager")
+    legend = _("Add social users folder plug-in")
+    icon_css_class = 'fa fa-fw fa-share-alt'
+
+    fields = field.Fields(ISocialUsersFolderPlugin).omit('__name__', '__parent__')
+    ajax_handler = 'add-social-users-folder.json'
+    edit_permission = 'system.manage'
+
+    def create(self, data):
+        return SocialUsersFolder()
+
+    def add(self, plugin):
+        context = query_utility(ISecurityManager)
+        context[plugin.prefix] = plugin
+
+    def nextURL(self):
+        return absolute_url(self.context, self.request, 'security-manager.html')
+
+
+@view_config(name='add-social-users-folder.json', context=ISite, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class SocialUsersFolderAJAXAddForm(AJAXAddForm, SocialUsersFolderAddForm):
+    """Social users folder plug-in add form, AJAX handler"""
+
+
+@pagelet_config(name='properties.html', context=ISocialUsersFolderPlugin, layer=IPyAMSLayer,
+                permission='system.view')
+class SocialUsersFolderEditForm(AdminDialogEditForm):
+    """Social users folder plug-in edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit social users folder plug-in properties")
+    icon_css_class = 'fa fa-fw fa-share-alt'
+
+    fields = field.Fields(ISocialUsersFolderPlugin).omit('__name__', '__parent__')
+    ajax_handler = 'properties.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(SocialUsersFolderEditForm, self).updateWidgets()
+        self.widgets['prefix'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=ISocialUsersFolderPlugin, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class SocialUsersFolderAJAXEditForm(AJAXEditForm, SocialUsersFolderEditForm):
+    """Social users folder plug-in edit form, AJAX handler"""
+
+
+#
+# Social users folder search views
+#
+
+@pagelet_config(name='search.html', context=ISocialUsersFolderPlugin, layer=IPyAMSLayer, permission='system.view')
+class SocialUsersFolderSearchView(AdminView, SearchView):
+    """Social users folder search view"""
+    
+    def __init__(self, context, request):
+        super(SocialUsersFolderSearchView, self).__init__(context, request)
+
+
+@adapter_config(context=(ISocialUsersFolderPlugin, IAdminLayer, SocialUsersFolderSearchView), provides=IPageHeader)
+class SocialUsersFolderSearchViewHeaderAdapter(ContextRequestViewAdapter):
+    """Social users folder search view header adapter"""
+
+    back_url = '#security-manager.html'
+    icon_class = 'fa fa-fw fa-share-alt'
+
+    @property
+    def title(self):
+        return self.context.title
+
+    subtitle = _("Search users")
+
+
+@view_config(name='search-results.html', context=ISocialUsersFolderPlugin, request_type=IPyAMSLayer,
+             permission='system.view')
+class SocialUsersFolderSearchResultsView(AdminView, SearchResultsView):
+    """Social users folder search results view table"""
+
+    id = 'social_users_folder_search_table'
+    title = _("Search results")
+    cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight datatable'}
+
+    def __init__(self, context, request):
+        super(SocialUsersFolderSearchResultsView, self).__init__(context, request)
+        apply_skin(self.request, 'PyAMS admin skin')
+
+
+@adapter_config(name='login', context=(ISocialUsersFolderPlugin, IAdminLayer, SocialUsersFolderSearchResultsView),
+                provides=IColumn)
+class IDColumn(I18nColumn, GetAttrColumn):
+    """Users ID column"""
+
+    _header = _("User ID")
+    attrName = 'user_id'
+    weight = 5
+
+
+@adapter_config(name='name', context=(ISocialUsersFolderPlugin, IAdminLayer, SocialUsersFolderSearchResultsView),
+                provides=IColumn)
+class NameColumn(I18nColumn, GetAttrColumn):
+    """Users name column"""
+
+    _header = _("Name")
+    attrName = 'title'
+    weight = 10
+
+
+@adapter_config(name='email', context=(ISocialUsersFolderPlugin, IAdminLayer, SocialUsersFolderSearchResultsView),
+                provides=IColumn)
+class EmailColumn(I18nColumn, GetAttrColumn):
+    """Users email column"""
+
+    _header = _("E-mail address")
+    attrName = 'email'
+    weight = 20
+
+
+@adapter_config(name='provider_name', context=(ISocialUsersFolderPlugin, IAdminLayer, SocialUsersFolderSearchResultsView),
+                provides=IColumn)
+class ProviderNameColumn(I18nColumn, GetAttrColumn):
+    """Users provider name column"""
+
+    _header = _("OAuth provider name")
+    attrName = 'provider_name'
+    weight = 30
+
+
+@adapter_config(name='registration_date', context=(ISocialUsersFolderPlugin, IAdminLayer, SocialUsersFolderSearchResultsView),
+                provides=IColumn)
+class RegistrationDateColumn(I18nColumn, GetAttrColumn):
+    """Users registration date column"""
+
+    _header = _("Registration date")
+    weight = 40
+
+    def getValue(self, obj):
+        dc = IZopeDublinCore(obj, None)
+        if dc is not None:
+            return format_datetime(dc.created)
+        else:
+            return '--'
+
+
+#
+# Social users views
+#
+
+@pagelet_config(name='properties.html', context=ISocialUser, layer=IPyAMSLayer, permission='system.view')
+class SocialUserEditForm(AdminDialogEditForm):
+    """Social user edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit user properties")
+    icon_css_class = 'fa fa-fw fa-share-alt'
+    label_css_class = 'control-label col-md-4'
+    input_css_class = 'col-md-8'
+
+    fields = field.Fields(ISocialUser).omit('__parent__', '__name__', 'attributes')
+
+    ajax_handler = 'properties.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(SocialUserEditForm, self).updateWidgets()
+        self.widgets['user_id'].mode = DISPLAY_MODE
+        self.widgets['provider_name'].mode = DISPLAY_MODE
+        self.widgets['registration_date'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=ISocialUser, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class SocialUserAJAXEditForm(AJAXEditForm, SocialUserEditForm):
+    """Social user edit form, AJAX view"""
+
+
+#
+# Social providers configuration
+#
+
+@viewlet_config(name='security-manager.social.menu', context=ISite, layer=IAdminLayer,
+                manager=ISecurityManagerMenu, permission='system.view', weight=10)
+class SecurityManagerSocialMenuItem(MenuItem):
+    """Security manager social menu"""
+
+    label = _("Social networks login")
+    url = '#social-providers.html'
+
+
+class SecurityManagerSocialProvidersTable(BaseTable):
+    """Security manager social providers table"""
+
+    id = 'social_providers_table'
+    title = _("Configured social networks login providers")
+    cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight datatable'}
+
+    @property
+    def data_attributes(self):
+        manager = query_utility(ISecurityManager)
+        if manager is not None:
+            return {'table': {'data-ams-location': absolute_url(ISocialLoginConfiguration(manager), self.request),
+                              'data-ams-plugins': 'pyams_security',
+                              'data-ams-plugin-pyams_security-src': '/--static--/pyams_security/js/security.js'},
+                    'tr': {'data-ams-element-name': lambda x: x.provider_name,
+                           'data-ams-url': lambda x: absolute_url(x, self.request, 'properties.html'),
+                           'data-toggle': 'modal'}}
+        return {}
+
+
+@adapter_config(name='icon', context=(Interface, IAdminLayer, SecurityManagerSocialProvidersTable), provides=IColumn)
+class SecurityManagerSocialProvidersIconColumn(ActionColumn):
+    """Security manager plugins icon column"""
+
+    weight = 1
+
+    def renderCell(self, item):
+        info = get_provider_info(item.provider_name)
+        return '<i class="{0}"></i>'.format(info.icon_class)
+
+
+@adapter_config(name='id', context=(Interface, IAdminLayer, SecurityManagerSocialProvidersTable), provides=IColumn)
+class SecurityManagerSocialProvidersIdColumn(I18nColumn, GetAttrColumn):
+    """Security manager plugins name column"""
+
+    _header = _("ID")
+    cssClasses = {'th': 'action'}
+    attrName = 'provider_id'
+    weight = 5
+
+
+@adapter_config(name='name', context=(Interface, IAdminLayer, SecurityManagerSocialProvidersTable), provides=IColumn)
+class SecurityManagerSocialProvidersNameColumn(I18nColumn, GetAttrColumn):
+    """Security manager plugins name column"""
+
+    _header = _("Name")
+    attrName = 'provider_name'
+    weight = 10
+
+
+@adapter_config(name='trash', context=(Interface, IAdminLayer, SecurityManagerSocialProvidersTable), provides=IColumn)
+class SecurityManagerSocialProvidersTrashColumn(ActionColumn):
+    """Security manager plugins trash column"""
+
+    url = "PyAMS_security.social.deleteProvider"
+    icon_class = 'fa fa-fw fa-trash'
+    icon_hint = _("Delete provider")
+    weight = 100
+    permission = 'system.manage'
+
+    def get_url(self, item):
+        return self.url
+
+
+@adapter_config(context=(ISite, IAdminLayer, SecurityManagerSocialProvidersTable), provides=IValues)
+class SecurityManagerSocialProvidersValuesAdapter(ContextRequestViewAdapter):
+    """Security manager social providers values adapter"""
+
+    @property
+    def values(self):
+        manager = query_utility(ISecurityManager)
+        if manager is not None:
+            configuration = ISocialLoginConfiguration(manager)
+            return list(configuration.values())
+        return ()
+
+
+@pagelet_config(name='social-providers.html', context=ISite, layer=IPyAMSLayer, permission='system.view')
+@implementer(IInnerPage)
+class SecurityManagerSocialProvidersView(AdminView, ContainerView):
+    """Security manager social providers view"""
+
+    table_class = SecurityManagerSocialProvidersTable
+
+    def __init__(self, context, request):
+        super(SecurityManagerSocialProvidersView, self).__init__(context, request)
+
+
+@adapter_config(context=(ISite, IAdminLayer, SecurityManagerSocialProvidersView), provides=IPageHeader)
+class SecurityManagerSocialProvidersHeaderAdapter(ContextRequestViewAdapter):
+    """Security manager social providers header adapter"""
+
+    icon_class = 'fa fa-fw fa-share-alt'
+    title = _("Security manager")
+    subtitle = _("Social networks login providers")
+
+
+@viewlet_config(name='security-manager.social.adding', context=ISite, view=SecurityManagerSocialProvidersTable,
+                layer=IAdminLayer, manager=IToolbarViewletManager, permission='system.manage')
+class SocialToolbarAddingsAction(ToolbarAction):
+    """Security manager social toolbar adding action"""
+
+    label = _("Add provider")
+    url = "add-social-provider.html"
+    modal_target = True
+
+
+@pagelet_config(name='add-social-provider.html', context=ISite, layer=IPyAMSLayer, permission='system.manage')
+class SocialProviderAddForm(AdminDialogAddForm):
+    """Social provider add form"""
+
+    title = _("Security manager")
+    legend = _("Add new social login provider")
+    icon_css_class = 'fa fa-fw fa-share-alt'
+    label_css_class = 'control-label col-md-4'
+    input_css_class = 'col-md-8'
+
+    fields = field.Fields(ISocialLoginProviderConnection).omit('__parent__', '__name__')
+
+    ajax_handler = 'add-social-provider.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(SocialProviderAddForm, self).updateWidgets()
+        self.widgets['provider_id'].input_css_class = 'col-md-3'
+
+    def create(self, data):
+        return SocialLoginProviderConnection()
+
+    def add(self, object):
+        manager = query_utility(ISecurityManager)
+        configuration = ISocialLoginConfiguration(manager)
+        configuration[object.provider_name] = object
+
+
+@subscriber(IDataExtractedEvent, form_selector=SocialProviderAddForm)
+def handle_new_social_provider_data_extraction(event):
+    """Handle new social provider data extraction"""
+    data = event.data
+    manager = query_utility(ISecurityManager)
+    configuration = ISocialLoginConfiguration(manager)
+    if data.get('provider_name') in configuration:
+        event.form.widgets.errors += (Invalid(_("This provider is already defined!")),)
+    for provider in configuration.values():
+        if data.get('provider_id') == provider.provider_id:
+            event.form.widgets.errors += (Invalid(_("This provider ID is already used!")),)
+            break
+
+
+@view_config(name='add-social-provider.json', context=ISite, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class SocialProviderAJAXAddForm(AJAXAddForm, SocialProviderAddForm):
+    """Social provider add form, AJAX view"""
+
+    def get_ajax_output(self, changes):
+        translate = self.request.localizer.translate
+        return {'status': 'reload',
+                'location': absolute_url(self.context, self.request, 'social-providers.html'),
+                'message': translate(_("Social provider was created successfully"))}
+
+
+@pagelet_config(name='properties.html', context=ISocialLoginProviderConnection, layer=IPyAMSLayer,
+                permission='system.view')
+class SocialProviderEditForm(AdminDialogEditForm):
+    """Social provider edit form"""
+
+    title = _("Security manager")
+    legend = _("Edit social login provider properties")
+    icon_css_class = 'fa fa-fw fa-share-alt'
+    label_css_class = 'control-label col-md-4'
+    input_css_class = 'col-md-8'
+
+    fields = field.Fields(ISocialLoginProviderConnection).omit('__parent__', '__name__')
+
+    ajax_handler = 'properties.json'
+    edit_permission = 'system.manage'
+
+    def updateWidgets(self, prefix=None):
+        super(SocialProviderEditForm, self).updateWidgets()
+        self.widgets['provider_name'].mode = DISPLAY_MODE
+        self.widgets['provider_id'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=ISocialLoginProviderConnection, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class SocialProviderAJAXEditForm(AJAXEditForm, SocialProviderEditForm):
+    """Social provider edit form, AJAX view"""
+
+
+@view_config(name='delete-provider.json', context=ISocialLoginConfiguration, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+def delete_social_provider(request):
+    """Delete social provider from security manager"""
+    translate = request.localizer.translate
+    name = request.params.get('provider_name')
+    if not name:
+        return {'status': 'message',
+                'messagebox': {'status': 'error',
+                               'content': translate(_("No provided provider_name argument!"))}}
+    if name not in request.context:
+        return {'status': 'message',
+                'messagebox': {'status': 'error',
+                               'content': translate(_("Given provider name doesn't exist!"))}}
+    del request.context[name]
+    return {'status': 'success'}
--- a/src/pyams_security/zmi/plugin/userfolder.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/zmi/plugin/userfolder.py	Mon Feb 23 17:55:05 2015 +0100
@@ -18,11 +18,10 @@
 
 # import interfaces
 from pyams_security.interfaces import IUsersFolderPlugin, ISecurityManager, ILocalUser, IUserRegistrationInfo
-from pyams_security.zmi.interfaces import ISecurityManagerToolbarAddingMenu, ISecurityManagerMenu
-from pyams_skin.interfaces.viewlet import IMenuItem, IToolbarViewletManager
+from pyams_security.zmi.interfaces import ISecurityManagerToolbarAddingMenu
+from pyams_skin.interfaces.viewlet import IToolbarViewletManager
 from pyams_skin.interfaces import IPageHeader
 from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces.data import IObjectData
 from pyams_zmi.layer import IAdminLayer
 from z3c.form.interfaces import DISPLAY_MODE, IDataExtractedEvent
 from z3c.table.interfaces import IColumn
@@ -36,7 +35,7 @@
 from pyams_security.plugin.userfolder import UsersFolder, User
 from pyams_security.zmi.utility import SecurityManagerPluginsTable
 from pyams_skin.skin import apply_skin
-from pyams_skin.viewlet.menu import MenuItem
+from pyams_skin.table import I18nColumn
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem, ToolbarAction
 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
 from pyams_utils.date import format_datetime
@@ -49,11 +48,15 @@
 from pyramid.view import view_config
 from z3c.form import field
 from z3c.table.column import GetAttrColumn
-from zope.interface import implementer, Interface, Invalid
+from zope.interface import Invalid
 
 from pyams_security import _
 
 
+#
+# Users folder views
+#
+
 @viewlet_config(name='add-users-folder.menu', context=ISite, layer=IAdminLayer,
                 view=SecurityManagerPluginsTable, manager=ISecurityManagerToolbarAddingMenu,
                 permission='system.manage', weight=10)
@@ -77,7 +80,7 @@
 
     fields = field.Fields(IUsersFolderPlugin).omit('__name__', '__parent__')
     ajax_handler = 'add-users-folder.json'
-    edit_permission = None
+    edit_permission = 'system.manage'
 
     def create(self, data):
         return UsersFolder()
@@ -123,19 +126,9 @@
     """Users folder plug-in edit form, AJAX handler"""
 
 
-@adapter_config(name='security.menu', context=(IUsersFolderPlugin, IAdminLayer, Interface, ISecurityManagerMenu),
-                provides=IMenuItem)
-@implementer(IObjectData)
-class UsersFolderContentsMenu(MenuItem):
-    """Users folder contents menu"""
-    
-    url = 'contents.html'
-    object_data = {'ams-target': '#content'}
-
-    @property
-    def label(self):
-        return self.context.title
-    
+#
+# Users folder search views
+#
 
 @pagelet_config(name='search.html', context=IUsersFolderPlugin, layer=IPyAMSLayer, permission='system.view')
 class UsersFolderSearchView(AdminView, SearchView):
@@ -149,6 +142,7 @@
 class UsersFolderSearchViewHeaderAdapter(ContextRequestViewAdapter):
     """Users folder search view header adapter"""
 
+    back_url = '#security-manager.html'
     icon_class = 'fa fa-fw fa-user'
 
     @property
@@ -174,49 +168,37 @@
 
 @adapter_config(name='login', context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchResultsView),
                 provides=IColumn)
-class LoginColumn(GetAttrColumn):
+class LoginColumn(I18nColumn, GetAttrColumn):
     """Users login column"""
 
     _header = _("Login")
     attrName = 'login'
     weight = 5
 
-    @property
-    def header(self):
-        return self.request.localizer.translate(self._header)
-
 
 @adapter_config(name='name', context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchResultsView),
                 provides=IColumn)
-class NameColumn(GetAttrColumn):
+class NameColumn(I18nColumn, GetAttrColumn):
     """Users name column"""
 
     _header = _("Name")
     attrName = 'title'
     weight = 10
 
-    @property
-    def header(self):
-        return self.request.localizer.translate(self._header)
-
 
 @adapter_config(name='email', context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchResultsView),
                 provides=IColumn)
-class EmailColumn(GetAttrColumn):
+class EmailColumn(I18nColumn, GetAttrColumn):
     """Users email column"""
 
     _header = _("E-mail address")
     attrName = 'email'
     weight = 20
 
-    @property
-    def header(self):
-        return self.request.localizer.translate(self._header)
-
 
 @adapter_config(name='registration_date', context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchResultsView),
                 provides=IColumn)
-class RegistrationDateColumn(GetAttrColumn):
+class RegistrationDateColumn(I18nColumn, GetAttrColumn):
     """Users registration date column"""
 
     _header = _("Registration date")
@@ -229,14 +211,10 @@
         else:
             return '--'
 
-    @property
-    def header(self):
-        return self.request.localizer.translate(self._header)
-
 
 @adapter_config(name='activation_date', context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchResultsView),
                 provides=IColumn)
-class ConfirmationDateColumn(GetAttrColumn):
+class ConfirmationDateColumn(I18nColumn, GetAttrColumn):
     """Users activation date column"""
 
     _header = _("Activation date")
@@ -248,15 +226,15 @@
         else:
             return '--'
 
-    @property
-    def header(self):
-        return self.request.localizer.translate(self._header)
 
+#
+# Users views
+#
 
 @viewlet_config(name='users-folder.toolbar.adding', context=IUsersFolderPlugin,
                 view=UsersFolderSearchView.search_form_factory, layer=IAdminLayer,
                 manager=IToolbarViewletManager, permission='system.manage')
-class UsersFolderAddAction(ToolbarAction):
+class LocalUserAddAction(ToolbarAction):
     """Users folder adding action"""
 
     label = _("Add user")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/zmi/security.py	Mon Feb 23 17:55:05 2015 +0100
@@ -0,0 +1,80 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import IDefaultProtectionPolicy, IRoleProtectedObject, IProtectedObject
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.interfaces.menu import IPropertiesMenu
+from pyams_zmi.layer import IAdminLayer
+
+# import packages
+from pyams_form.form import AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogEditForm
+from pyramid.view import view_config
+from z3c.form import field
+
+from pyams_security import _
+
+
+@viewlet_config(name='protected-object-roles.menu', context=IDefaultProtectionPolicy, layer=IAdminLayer,
+                manager=IPropertiesMenu, permission='system.view', weight=10)
+class ProtectedObjectRolesMenuItem(MenuItem):
+    """Protected object roles menu item"""
+
+    label = _("Access rules...")
+    icon_class = 'fa fa-fw fa-key'
+    url = 'protected-object-roles.html'
+    modal_target = True
+
+
+@pagelet_config(name='protected-object-roles.html', context=IDefaultProtectionPolicy, layer=IPyAMSLayer,
+                permission='system.view')
+class ProtectedObjectRolesEditForm(AdminDialogEditForm):
+    """Protected object roles edit form"""
+
+    @property
+    def title(self):
+        return self.context.title
+
+    legend = _("Edit local roles")
+    icon_css_class = 'fa fa-fw fa-key'
+    label_css_class = 'control-label col-md-4'
+    input_css_class = 'col-md-8'
+
+    ajax_handler = 'protected-object-roles.json'
+    edit_permission = 'system.manage'
+
+    @property
+    def fields(self):
+        fields = field.Fields(IProtectedObject) + \
+                 field.Fields(self.context.roles_interface)
+        return fields
+
+    def updateWidgets(self, prefix=None):
+        super(ProtectedObjectRolesEditForm, self).updateWidgets()
+        translate = self.request.localizer.translate
+        self.widgets['everyone_permission'].noValueMessage = translate(_("(inherit from parent)"))
+        self.widgets['authenticated_permission'].noValueMessage = translate(_("(inherit from parent)"))
+
+
+@view_config(name='protected-object-roles.json', context=IDefaultProtectionPolicy, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+class ProtectedObjectRolesAJAXEditForm(AJAXEditForm, ProtectedObjectRolesEditForm):
+    """Protected object roles edit form, AJAX view"""
--- a/src/pyams_security/zmi/utility.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/zmi/utility.py	Mon Feb 23 17:55:05 2015 +0100
@@ -29,15 +29,17 @@
 
 # import packages
 from pyams_form.form import AJAXEditForm
+from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_security.zmi.widget import OrderedPluginsFieldWidget
 from pyams_skin.container import ContainerView
-from pyams_skin.table import BaseTable, DefaultElementEditorAdapter, ActionColumn
+from pyams_skin.table import BaseTable, DefaultElementEditorAdapter, ActionColumn, I18nColumn
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_skin.viewlet.toolbar import ToolbarMenu
 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
 from pyams_utils.registry import query_utility
 from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogEditForm
@@ -45,6 +47,8 @@
 from pyramid.url import resource_url
 from pyramid.view import view_config
 from z3c.form import field
+from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
+from z3c.table.column import GetAttrColumn
 from zope.interface import implementer, Interface
 
 from pyams_security import _
@@ -82,13 +86,29 @@
     title = _("Authentication and users directory plug-ins")
     cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight datatable'}
 
+    @property
+    def data_attributes(self):
+        manager = query_utility(ISecurityManager)
+        attributes = super(SecurityManagerPluginsTable, self).data_attributes
+        table_attrs = {'data-ams-location': absolute_url(manager, self.request),
+                       'data-ams-plugins': 'pyams_security',
+                       'data-ams-plugin-pyams_security-src': '/--static--/pyams_security/js/security.js'}
+        if 'table' in attributes:
+            attributes['table'].update(table_attrs)
+        else:
+            attributes['table'] = table_attrs
+        return attributes
+
 
 @adapter_config(name='search', context=(Interface, IAdminLayer, SecurityManagerPluginsTable), provides=IColumn)
 class SecurityManagerPluginsSearchColumn(ActionColumn):
     """Security manager plugins search column"""
 
+    icon_class = 'fa fa-fw fa-search'
+    icon_hint = _("See plug-in contents")
+
     url = "search.html"
-    weight = 100
+    weight = 1
 
     def renderCell(self, item):
         if not IDirectorySearchPlugin.providedBy(item):
@@ -96,6 +116,34 @@
         return super(SecurityManagerPluginsSearchColumn, self).renderCell(item)
 
 
+@adapter_config(name='name', context=(Interface, IAdminLayer, SecurityManagerPluginsTable), provides=IColumn)
+class SecurityManagerPluginsNameColumn(I18nColumn, GetAttrColumn):
+    """Security manager plugins name column"""
+
+    _header = _("Name")
+    attrName = 'title'
+    weight = 10
+
+
+@adapter_config(name='trash', context=(Interface, IAdminLayer, SecurityManagerPluginsTable), provides=IColumn)
+class SecurityManagerPluginsTrashColumn(ActionColumn):
+    """Security manager plugins trash column"""
+
+    url = "PyAMS_security.plugins.deletePlugin"
+    icon_class = 'fa fa-fw fa-trash'
+    icon_hint = _("Delete plug-in")
+    weight = 100
+    permission = 'system.manage'
+
+    def renderCell(self, item):
+        if item.__name__ == '__system__':  # can't delete system manager login plug-in!!
+            return ''
+        return super(SecurityManagerPluginsTrashColumn, self).renderCell(item)
+
+    def get_url(self, item):
+        return self.url
+
+
 @adapter_config(context=(ISite, IAdminLayer, SecurityManagerPluginsTable), provides=IValues)
 class SecurityManagerValuesAdapter(ContextRequestViewAdapter):
     """Security manager values adapter"""
@@ -140,7 +188,7 @@
 
 
 @viewlet_config(name='security-manager.properties.menu', context=ISite, layer=IAdminLayer,
-                manager=ISecurityManagerMenu, permission='system.view')
+                manager=ISecurityManagerMenu, permission='system.view', weight=1)
 class SecurityManagerPropertiesMenuItem(MenuItem):
     """Security manager properties menu"""
 
@@ -160,10 +208,12 @@
     title = _("System security manager")
     legend = _("Security manager properties")
     icon_css_class = 'fa fa-fw fa-lock'
-    label_css_class = 'control-label col-md-4'
-    input_css_class = 'col-md-8'
+    label_css_class = 'control-label col-md-5'
+    input_css_class = 'col-md-7'
 
     fields = field.Fields(ISecurityManager)
+    fields['enable_social_login'].widgetFactory = SingleCheckBoxFieldWidget
+    fields['open_registration'].widgetFactory = SingleCheckBoxFieldWidget
     fields['credentials_plugins_names'].widgetFactory = OrderedPluginsFieldWidget
     fields['authentication_plugins_names'].widgetFactory = OrderedPluginsFieldWidget
     fields['directory_plugins_names'].widgetFactory = OrderedPluginsFieldWidget
@@ -173,8 +223,52 @@
     def getContent(self):
         return query_utility(ISecurityManager)
 
+    def update(self):
+        super(SecurityManagerEditForm, self).update()
+        self.add_group(NamedWidgetsGroup('social_group', self.widgets,
+                                         ('enable_social_login', 'social_users_folder',
+                                          'authomatic_secret', 'social_login_use_popup'),
+                                         legend=_("Enable social login?"),
+                                         css_class='inner',
+                                         switch=True,
+                                         checkbox_switch=True,
+                                         checkbox_field=ISecurityManager['enable_social_login']))
+        self.add_group(NamedWidgetsGroup('registry_group', self.widgets,
+                                         ('open_registration', 'users_folder'),
+                                         legend=_("Enable free registration?"),
+                                         css_class='inner',
+                                         switch=True,
+                                         checkbox_switch=True,
+                                         checkbox_field=ISecurityManager['open_registration']))
+        plugins_group = NamedWidgetsGroup('plugins_group', self.widgets,
+                                          ('credentials_plugins_names', 'authentication_plugins_names',
+                                           'directory_plugins_names'),
+                                          legend=_("Plug-ins"),
+                                          css_class='inner')
+        plugins_group.label_css_class = 'control-label col-md-4'
+        plugins_group.input_css_class = 'col-md-8'
+        self.add_group(plugins_group)
+
 
 @view_config(name='properties.json', context=ISecurityManager, request_type=IPyAMSLayer,
              permission='system.manage', renderer='json', xhr=True)
 class SecurityManagerAJAXEditForm(AJAXEditForm, SecurityManagerEditForm):
     """Security manager edit form, AJAX view"""
+
+
+@view_config(name='delete-plugin.json', context=ISecurityManager, request_type=IPyAMSLayer,
+             permission='system.manage', renderer='json', xhr=True)
+def delete_security_manager_plugin(request):
+    """Delete plug-in from security manager"""
+    translate = request.localizer.translate
+    name = request.params.get('plugin_name')
+    if not name:
+        return {'status': 'message',
+                'messagebox': {'status': 'error',
+                               'content': translate(_("No provided plugin_name argument!"))}}
+    if name not in request.context:
+        return {'status': 'message',
+                'messagebox': {'status': 'error',
+                               'content': translate(_("Given plug-in name doesn't exist!"))}}
+    del request.context[name]
+    return {'status': 'success'}
--- a/src/pyams_security/zmi/widget/__init__.py	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/zmi/widget/__init__.py	Mon Feb 23 17:55:05 2015 +0100
@@ -9,8 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_form.interfaces.form import IFormLayer
-from pyams_form.widget import widgettemplate_config
 
 __docformat__ = 'restructuredtext'
 
@@ -18,10 +16,12 @@
 # import standard library
 
 # import interfaces
+from pyams_form.interfaces.form import IFormLayer
 from z3c.form.interfaces import IDataConverter
 from zope.schema.interfaces import ITuple
 
 # import packages
+from pyams_form.widget import widgettemplate_config
 from pyams_utils.adapter import adapter_config
 from z3c.form.browser.widget import HTMLFormElement
 from z3c.form.converter import BaseDataConverter
--- a/src/pyams_security/zmi/widget/templates/ordered-list-input.pt	Sun Feb 22 14:52:32 2015 +0100
+++ b/src/pyams_security/zmi/widget/templates/ordered-list-input.pt	Mon Feb 23 17:55:05 2015 +0100
@@ -1,7 +1,7 @@
 <table class="table table-bordered table-striped table-hover table-tight table-dnd"
 	   data-ams-tablednd-drop-target="PyAMS_security.plugins.changeOrder"
-	   data-ams-plugins="security"
-	   data-ams-plugin-security-src="/--static--/pyams_security/js/security.js"
+	   data-ams-plugins="pyams_security"
+	   data-ams-plugin-pyams_security-src="/--static--/pyams_security/js/security.min.js"
 	   tal:attributes="id string:${view/field/getName}_list;
 					   data-ams-input-name view/name;">
 	<input type="hidden"