# HG changeset patch # User Thierry Florac # Date 1424710505 -3600 # Node ID 94e76f8e9828c0b53bd4992316f962e5fdfa74c0 # Parent 5595823c66f1327371a16de6250e7a9cd65e6a86 Finalized social plug-ins, groups and roles integration diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/__init__.py --- 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'}}) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/include.py --- 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 diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/interfaces/__init__.py --- 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""" diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.mo Binary file src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.mo has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po --- 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 \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 ?" diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/locales/pyams_security.pot --- 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 \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 "" diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/permission.py --- 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/plugin/group.py --- /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 +# 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] diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/plugin/social.py --- /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 +# 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/plugin/userfolder.py --- 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 () diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/principal.py --- 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 ''.format(self.id) + return 'MissingPrincipal: {0}'.format(self.id) def __eq__(self, other): return isinstance(other, PrincipalInfo) and (self.id == other.id) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/property.py --- /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 +# 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/behance.ico Binary file src/pyams_security/resources/img/behance.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/bitbucket.ico Binary file src/pyams_security/resources/img/bitbucket.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/bitly.ico Binary file src/pyams_security/resources/img/bitly.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/cosm.ico Binary file src/pyams_security/resources/img/cosm.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/deviantart.ico Binary file src/pyams_security/resources/img/deviantart.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/facebook.ico Binary file src/pyams_security/resources/img/facebook.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/flickr.ico Binary file src/pyams_security/resources/img/flickr.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/foursquare.ico Binary file src/pyams_security/resources/img/foursquare.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/github.ico Binary file src/pyams_security/resources/img/github.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/google.ico Binary file src/pyams_security/resources/img/google.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/linkedin.ico Binary file src/pyams_security/resources/img/linkedin.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/meetup.ico Binary file src/pyams_security/resources/img/meetup.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/paypal.ico Binary file src/pyams_security/resources/img/paypal.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/plurk.ico Binary file src/pyams_security/resources/img/plurk.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/reddit.ico Binary file src/pyams_security/resources/img/reddit.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/tumblr.ico Binary file src/pyams_security/resources/img/tumblr.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/twitter.ico Binary file src/pyams_security/resources/img/twitter.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/ubuntuone.ico Binary file src/pyams_security/resources/img/ubuntuone.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/viadeo.ico Binary file src/pyams_security/resources/img/viadeo.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/vimeo.ico Binary file src/pyams_security/resources/img/vimeo.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/vk.ico Binary file src/pyams_security/resources/img/vk.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/windows_live.ico Binary file src/pyams_security/resources/img/windows_live.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/xero.ico Binary file src/pyams_security/resources/img/xero.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/xing.ico Binary file src/pyams_security/resources/img/xing.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/yahoo.ico Binary file src/pyams_security/resources/img/yahoo.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/yammer.ico Binary file src/pyams_security/resources/img/yammer.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/img/yandex.ico Binary file src/pyams_security/resources/img/yandex.ico has changed diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/js/security.js --- 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: '  ' + 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: '  ' + 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(); + } + }); + } + }); + } + } } }; diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/resources/js/security.min.js --- 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:'  '+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:'  '+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 diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/role.py --- 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/schema.py --- /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 +# 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/security.py --- /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 +# 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 [] diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/utility.py --- 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/views/oauth.py --- 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: diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/views/templates/social-login.pt --- 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"> Login with: - Facebook - Twitter - Google + + + + + diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/views/utility.py --- /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 +# 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)] diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/widget/__init__.py --- /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 +# 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)) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/widget/interfaces.py --- /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 +# 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""" diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/widget/templates/principal-display.pt --- /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 @@ + diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/widget/templates/principal-input.pt --- /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 @@ + diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/widget/templates/principals-set-display.pt --- /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 @@ + diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/widget/templates/principals-set-input.pt --- /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 @@ + diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/plugin/group.py --- /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 +# 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) diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/plugin/social.py --- /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 +# 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 ''.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'} diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/plugin/userfolder.py --- 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") diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/security.py --- /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 +# 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""" diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/utility.py --- 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'} diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/widget/__init__.py --- 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 diff -r 5595823c66f1 -r 94e76f8e9828 src/pyams_security/zmi/widget/templates/ordered-list-input.pt --- 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 @@