--- 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> ' + 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> ' + 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> '+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> '+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"