# HG changeset patch # User Thierry Florac # Date 1424339609 -3600 # Node ID f04e1d0a07231d926ad22fd1899a85d18c696aef First commit diff -r 000000000000 -r f04e1d0a0723 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,19 @@ + +syntax: regexp +^develop-eggs$ +syntax: regexp +^parts$ +syntax: regexp +^bin$ +syntax: regexp +^\.installed\.cfg$ +syntax: regexp +^\.settings$ +syntax: regexp +^build$ +syntax: regexp +^dist$ +syntax: regexp +^\.idea$ +syntax: regexp +.*\.pyc$ diff -r 000000000000 -r f04e1d0a0723 .installed.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.installed.cfg Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,71 @@ +[buildout] +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs/pyams-security.egg-link +parts = package i18n pyflakes test + +[package] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pshell + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pviews + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pcreate + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/proutes + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/prequest + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pdistreport + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/ptweens + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pserve +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.1-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +eggs = pyams_security + pyramid + zope.component + zope.interface +eggs-directory = /var/local/env/pyams/eggs +recipe = zc.recipe.egg + +[i18n] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pybabel + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pot-create + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/polint +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.1-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +eggs = babel + lingua +eggs-directory = /var/local/env/pyams/eggs +recipe = zc.recipe.egg + +[pyflakes] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pyflakes + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/pyflakes +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.1-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +eggs = pyflakes +eggs-directory = /var/local/env/pyams/eggs +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["src"] +recipe = zc.recipe.egg +scripts = pyflakes + +[test] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_security/parts/test + /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/test +__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.1-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-1482e89f68d85eea27f4ed7749df2819 +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_security/develop-eggs +eggs = pyams_security [test] +eggs-directory = /var/local/env/pyams/eggs +location = /home/tflorac/Dropbox/src/PyAMS/pyams_security/parts/test +recipe = zc.recipe.testrunner +script = /home/tflorac/Dropbox/src/PyAMS/pyams_security/bin/test diff -r 000000000000 -r f04e1d0a0723 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,42 @@ +Zope Public License (ZPL) Version 2.1 +===================================== + +A copyright notice accompanies this license document that identifies +the copyright holders. + +This license has been certified as open source. It has also been designated +as GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of the + copyright holders. Use of them is covered by separate agreement with the + copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of any + change. + + +Disclaimer +========== + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff -r 000000000000 -r f04e1d0a0723 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,5 @@ +include *.txt +recursive-include docs * +recursive-include src * +global-exclude *.pyc +global-exclude *.*~ diff -r 000000000000 -r f04e1d0a0723 bootstrap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootstrap.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,178 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# 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. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os +import shutil +import sys +import tempfile + +from optparse import OptionParser + +tmpeggs = tempfile.mkdtemp() + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --find-links to point to local resources, you can keep +this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("-v", "--version", help="use a specific zc.buildout version") + +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --version, the " + "bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", "--config-file", + help=("Specify the path to the buildout configuration " + "file to be used.")) +parser.add_option("-f", "--find-links", + help=("Specify a URL to search for buildout releases")) +parser.add_option("--allow-site-packages", + action="store_true", default=False, + help=("Let bootstrap.py use existing site packages")) + + +options, args = parser.parse_args() + +###################################################################### +# load/install setuptools + +try: + if options.allow_site_packages: + import setuptools + import pkg_resources + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +ez = {} +exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) + +if not options.allow_site_packages: + # ez_setup imports site, which adds site packages + # this will remove them from the path to ensure that incompatible versions + # of setuptools are not in the path + import site + # inside a virtualenv, there is no 'getsitepackages'. + # We can't remove these reliably + if hasattr(site, 'getsitepackages'): + for sitepackage_path in site.getsitepackages(): + sys.path[:] = [x for x in sys.path if sitepackage_path not in x] + +setup_args = dict(to_dir=tmpeggs, download_delay=0) +ez['use_setuptools'](**setup_args) +import setuptools +import pkg_resources + +# This does not (always?) update the default working set. We will +# do it. +for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +###################################################################### +# Install buildout + +ws = pkg_resources.working_set + +cmd = [sys.executable, '-c', + 'from setuptools.command.easy_install import main; main()', + '-mZqNxd', tmpeggs] + +find_links = os.environ.get( + 'bootstrap-testing-find-links', + options.find_links or + ('http://downloads.buildout.org/' + if options.accept_buildout_test_releases else None) + ) +if find_links: + cmd.extend(['-f', find_links]) + +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +requirement = 'zc.buildout' +version = options.version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + + def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( + search_path=[setuptools_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +import subprocess +if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: + raise Exception( + "Failed to execute command:\n%s" % repr(cmd)[1:-1]) + +###################################################################### +# Import and run buildout + +ws.add_entry(tmpeggs) +ws.require(requirement) +import zc.buildout.buildout + +if not [a for a in args if '=' not in a]: + args.append('bootstrap') + +# if -c was provided, we push it back into args for buildout' main function +if options.config_file is not None: + args[0:0] = ['-c', options.config_file] + +zc.buildout.buildout.main(args) +shutil.rmtree(tmpeggs) diff -r 000000000000 -r f04e1d0a0723 buildout.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/buildout.cfg Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,61 @@ +[buildout] +eggs-directory = /var/local/env/pyams/eggs + +socket-timeout = 3 +show-picked-versions = true +newest = false + +allow-hosts = + bitbucket.org + *.python.org + *.sourceforge.net + github.com + +#extends = http://download.ztfy.org/webapp/ztfy.webapp.dev.cfg +versions = versions +newest = false +#allow-picked-versions = false + +src = src +develop = . + +parts = + package + i18n + pyflakes + test + +[package] +recipe = zc.recipe.egg +eggs = + authomatic + pyams_security + pyramid + zope.component + zope.interface + zope.password + +[i18n] +recipe = zc.recipe.egg +eggs = + babel + lingua + +[pyflakes] +recipe = zc.recipe.egg +eggs = pyflakes +scripts = pyflakes +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["${buildout:src}"] + +[pyflakesrun] +recipe = collective.recipe.cmd +on_install = true +cmds = ${buildout:develop}/bin/${pyflakes:scripts} + +[test] +recipe = zc.recipe.testrunner +eggs = pyams_security [test] + +[versions] +pyams_security = 0.1.0 diff -r 000000000000 -r f04e1d0a0723 docs/HISTORY.txt diff -r 000000000000 -r f04e1d0a0723 docs/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/README.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,85 @@ +====================== +PyAMS security package +====================== + +Roles et permissions +-------------------- + - déclaration des permissions + - déclaration des rôles + - association rôles => permissions + - gestion des rôles locaux (Cf pyramid_localroles) + - fonctions "config.register_permission()" et "config.register_role()" + + +Rôles locaux +------------ + - interface ILocalRoles(Interface) + __roles__: {'role.id1': {'principal.id1', 'principal.id1'...}, + 'role.id2': {'principal.id1', 'principal.id3'...}} + - catalogage des rôles attribués pour recherche rapide + * les rôles attribués à un utilisateur font partie de ses "groupes" + + +Modules d'extraction de credentials +----------------------------------- + * extract_credentials + -> request + <- Credentials + + - HTTP basic + - Form -> mémoriser login/password en session + + +Modules d'authentification +-------------------------- + * authenticate + -> Credentials, request + <- principal_id + + - Users locaux avec mot de passe + - LDAP + - SQLAlchemy + - (OAuth (authomatic)) + * chaque plug-in peut-être présent en plusieurs exemplaires, chacun avec un préfixe propre + * exemple pour LDAP avec plusieurs branches autorisées + + +Modules de stockage des utilisateurs +------------------------------------ + * find_principal + -> principal_id + <- PrincipalInfo + + - LDAP => préfixe = ldap: => ldap:uid (ou ldap:dn ?) + - SQLAlchemy => préfixe = sa.session_name: => sa.AGENTS:12345, sa.CLIENTS:45678 + - Users OAuth => préfixe = oauth: => oauth:twitter.123456 + - Users locaux => préfixe = local: => local:uid (ou ldap:email ?) + - Groupes => stockage d'une liste d'ID d'utilisateurs + préfixe = group: => group:managers + + - recherche des utilisateurs + + +Module d'encodage des mots de passe +----------------------------------- + - encodage et vérification des mots de passe + + +Utilitaire d'authentification +----------------------------- + - prise en charge des modules + - application des fonctions d'authentification + + +Annotations des utilisateurs +---------------------------- + - interface générique pour associer des métadonnées à un ID utilisateur + + +Événements +---------- + - AuthenticatedPrincipal + -> notifié lorsqu'un utilisateur s'authentifie avec succès (HTTP, Form, OAuth) + -> enregistrement des credentials en session (si besoin du mot de passe) + -> permet d'enregistrer les propriétés de l'utilisateur dans un plug-in de stockage + si l'inscription "libre" est autorisée diff -r 000000000000 -r f04e1d0a0723 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,70 @@ +# +# 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. +# + +""" +This module contains pyams_security package +""" +import os +from setuptools import setup, find_packages + +DOCS = os.path.join(os.path.dirname(__file__), + 'docs') + +README = os.path.join(DOCS, 'README.txt') +HISTORY = os.path.join(DOCS, 'HISTORY.txt') + +version = '0.1.0' +long_description = open(README).read() + '\n\n' + open(HISTORY).read() + +tests_require = [] + +setup(name='pyams_security', + version=version, + description="PyAMS security interfaces and classes", + long_description=long_description, + classifiers=[ + "License :: OSI Approved :: Zope Public License", + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='Pyramid PyAMS security authentication', + author='Thierry Florac', + author_email='tflorac@ulthar.net', + url='http://hg.ztfy.org/pyams/pyams_security', + license='ZPL', + packages=find_packages('src'), + package_dir={'': 'src'}, + namespace_packages=[], + include_package_data=True, + package_data={'': ['*.zcml', '*.txt', '*.pt', '*.pot', '*.po', '*.mo', '*.png', '*.gif', '*.jpeg', '*.jpg', '*.css', '*.js']}, + zip_safe=False, + # uncomment this to be able to run tests with setup.py + test_suite="pyams_security.tests.test_utilsdocs.test_suite", + tests_require=tests_require, + extras_require=dict(test=tests_require), + install_requires=[ + 'setuptools', + # -*- Extra requirements: -*- + 'authomatic', + 'fanstatic', + 'pyramid', + 'zope.component', + 'zope.interface', + 'zope.password' + ], + entry_points={ + 'fanstatic.libraries': [ + 'pyams_security = pyams_security:library' + ] + }) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/PKG-INFO Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,103 @@ +Metadata-Version: 1.1 +Name: pyams-security +Version: 0.1.0 +Summary: PyAMS security interfaces and classes +Home-page: http://hg.ztfy.org/pyams/pyams_security +Author: Thierry Florac +Author-email: tflorac@ulthar.net +License: ZPL +Description: ====================== + PyAMS security package + ====================== + + Roles et permissions + -------------------- + - déclaration des permissions + - déclaration des rôles + - association rôles => permissions + - gestion des rôles locaux (Cf pyramid_localroles) + - fonctions "config.register_permission()" et "config.register_role()" + + + Rôles locaux + ------------ + - interface ILocalRoles(Interface) + __roles__: {'role.id1': {'principal.id1', 'principal.id1'...}, + 'role.id2': {'principal.id1', 'principal.id3'...}} + - catalogage des rôles attribués pour recherche rapide + * les rôles attribués à un utilisateur font partie de ses "groupes" + + + Modules d'extraction de credentials + ----------------------------------- + * extract_credentials + -> request + <- Credentials + + - HTTP basic + - Form -> mémoriser login/password en session + + + Modules d'authentification + -------------------------- + * authenticate + -> Credentials, request + <- principal_id + + - Users locaux avec mot de passe + - LDAP + - SQLAlchemy + - (OAuth (authomatic)) + * chaque plug-in peut-être présent en plusieurs exemplaires, chacun avec un préfixe propre + * exemple pour LDAP avec plusieurs branches autorisées + + + Modules de stockage des utilisateurs + ------------------------------------ + * find_principal + -> principal_id + <- PrincipalInfo + + - LDAP => préfixe = ldap: => ldap:uid (ou ldap:dn ?) + - SQLAlchemy => préfixe = sa.session_name: => sa.AGENTS:12345, sa.CLIENTS:45678 + - Users OAuth => préfixe = oauth: => oauth:twitter.123456 + - Users locaux => préfixe = local: => local:uid (ou ldap:email ?) + - Groupes => stockage d'une liste d'ID d'utilisateurs + préfixe = group: => group:managers + + - recherche des utilisateurs + + + Module d'encodage des mots de passe + ----------------------------------- + - encodage et vérification des mots de passe + + + Utilitaire d'authentification + ----------------------------- + - prise en charge des modules + - application des fonctions d'authentification + + + Annotations des utilisateurs + ---------------------------- + - interface générique pour associer des métadonnées à un ID utilisateur + + + Événements + ---------- + - AuthenticatedPrincipal + -> notifié lorsqu'un utilisateur s'authentifie avec succès (HTTP, Form, OAuth) + -> enregistrement des credentials en session (si besoin du mot de passe) + -> permet d'enregistrer les propriétés de l'utilisateur dans un plug-in de stockage + si l'inscription "libre" est autorisée + + + +Keywords: Pyramid PyAMS security authentication +Platform: UNKNOWN +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python +Classifier: Framework :: Pyramid +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/SOURCES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/SOURCES.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,43 @@ +MANIFEST.in +setup.py +docs/HISTORY.txt +docs/README.txt +src/pyams_security/__init__.py +src/pyams_security/configure.zcml +src/pyams_security/credential.py +src/pyams_security/include.py +src/pyams_security/permission.py +src/pyams_security/principal.py +src/pyams_security/role.py +src/pyams_security/site.py +src/pyams_security/utility.py +src/pyams_security.egg-info/PKG-INFO +src/pyams_security.egg-info/SOURCES.txt +src/pyams_security.egg-info/dependency_links.txt +src/pyams_security.egg-info/entry_points.txt +src/pyams_security.egg-info/namespace_packages.txt +src/pyams_security.egg-info/not-zip-safe +src/pyams_security.egg-info/requires.txt +src/pyams_security.egg-info/top_level.txt +src/pyams_security/doctests/README.txt +src/pyams_security/interfaces/__init__.py +src/pyams_security/plugin/__init__.py +src/pyams_security/plugin/admin.py +src/pyams_security/plugin/http.py +src/pyams_security/resources/js/authomatic.js +src/pyams_security/resources/js/authomatic.min.js +src/pyams_security/resources/js/security.js +src/pyams_security/resources/js/security.min.js +src/pyams_security/tests/__init__.py +src/pyams_security/tests/test_utilsdocs.py +src/pyams_security/tests/test_utilsdocstrings.py +src/pyams_security/views/__init__.py +src/pyams_security/views/login.py +src/pyams_security/views/oauth.py +src/pyams_security/views/templates/social-login.pt +src/pyams_security/zmi/__init__.py +src/pyams_security/zmi/configure.zcml +src/pyams_security/zmi/interfaces.py +src/pyams_security/zmi/utility.py +src/pyams_security/zmi/plugin/__init__.py +src/pyams_security/zmi/plugin/admin.py \ No newline at end of file diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/dependency_links.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/dependency_links.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/entry_points.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/entry_points.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,3 @@ +[fanstatic.libraries] +pyams_security = pyams_security:library + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/namespace_packages.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/namespace_packages.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/not-zip-safe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/not-zip-safe Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/requires.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/requires.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,8 @@ +setuptools +fanstatic +pyramid +zope.component +zope.interface +zope.password + +[test] diff -r 000000000000 -r f04e1d0a0723 src/pyams_security.egg-info/top_level.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security.egg-info/top_level.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,1 @@ +pyams_security diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,37 @@ +# +# 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 fanstatic import Library + +# import interfaces + +# import packages + +from pyramid.i18n import TranslationStringFactory +_ = TranslationStringFactory('pyams_security') + + +library = Library('pyams_security', 'resources') + + +def includeme(config): + """Pyramid include + + Split in another package to remove cyclic dependencies with TranslationStringFactory + """ + from .include import include_package + include_package(config) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/configure.zcml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/configure.zcml Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/credential.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/credential.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,37 @@ +# +# 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 ICredentials + +# import packages +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(ICredentials) +class Credentials(object): + """Credentials class""" + + prefix = FieldProperty(ICredentials['prefix']) + id = FieldProperty(ICredentials['id']) + attributes = FieldProperty(ICredentials['attributes']) + + def __init__(self, prefix, id, **attributes): + self.prefix = prefix + self.id = id + self.attributes.update(**attributes) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/doctests/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/doctests/README.txt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,3 @@ +====================== +pyams_security package +====================== diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/include.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/include.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,58 @@ +# +# 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 + +# import packages +from pyams_security.permission import register_permission +from pyams_security.plugin import PluginSelector +from pyams_security.role import register_role +from pyams_security.utility import get_principal + + +def include_package(config): + """Pyramid include""" + # add translations + config.add_translation_dirs('pyams_security:locales') + + # add configuration directives + config.add_directive('register_permission', register_permission) + config.add_directive('register_role', register_role) + config.add_request_method(get_principal, 'principal', reify=True) + + # add subscribers predicate + config.add_subscriber_predicate('plugin_selector', PluginSelector) + + # 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 + except ImportError: + config.scan(ignore='pyams_security.zmi') + else: + config.scan() + + if hasattr(config, 'load_zcml'): + config.load_zcml('configure.zcml') diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/interfaces/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/interfaces/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,500 @@ +# +# 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 re + +# import interfaces +from pyams_skin.interfaces import IContentSearch +from zope.annotation.interfaces import IAttributeAnnotatable +from zope.container.interfaces import IContainer +from zope.location.interfaces import IContained + +# import packages +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 pyams_security import _ + + +class IPermission(Interface): + """Permission utility class""" + + id = TextLine(title="Unique ID", + required=True) + + title = TextLine(title="Title", + required=True) + + description = Text(title="Description", + required=False) + + +class IRole(Interface): + """Role utility class""" + + id = TextLine(title="Unique ID", + required=True) + + title = TextLine(title="Title", + required=True) + + description = Text(title="Description", + required=False) + + permissions = Set(title="Permissions", + description="ID of role's permissions", + value_type=TextLine(), + required=False) + + +class IPrincipalInfo(Interface): + """Principal info class + + This is the generic interface of objects defined in request 'principal' attribute + """ + + id = TextLine(title="Globally unique ID", + required=True) + + title = TextLine(title="Principal name", + required=True) + + groups = Set(title="Principal groups", + description="IDs of principals to which this principal directly belongs", + value_type=TextLine()) + + +class ICredentials(Interface): + """Credentials interface""" + + prefix = TextLine(title="Credentials plug-in prefix", + description="Prefix of plug-in which extracted credentials") + + id = TextLine(title="Credentials ID") + + attributes = Dict(title="Credentials attributes", + description="Attributes dictionary defined by each credentials plug-in", + required=False, + default={}) + + +# +# Credentials, authentication and directory plug-ins +# + +class IPluginEvent(Interface): + """Plug-in event interface""" + + plugin = Attribute("Event plug-in name") + + +class IAuthenticatedPrincipalEvent(IPluginEvent): + """Authenticated principal event interface""" + + principal_id = Attribute("Authenticated principal ID") + + infos = Attribute("Event custom infos") + + +@implementer(IAuthenticatedPrincipalEvent) +class AuthenticatedPrincipalEvent(object): + """Authenticated principal event""" + + def __init__(self, plugin, principal_id, **infos): + self.plugin = plugin + self.principal_id = principal_id + self.infos = infos + + +class IPlugin(IContained, IAttributeAnnotatable): + """Basic authentication plug-in interface""" + + containers('pyams_security.interfaces.IAuthentication') + + prefix = TextLine(title=_("Plug-in prefix"), + description=_("This prefix is mainly used by authentication plug-ins to mark principals")) + + title = TextLine(title=_("Plug-in title"), + required=False) + + enabled = Bool(title=_("Enabled plug-in?"), + description=_("You can choose to disable any plug-in..."), + required=True, + default=True) + + +class ICredentialsInfo(Interface): + """Credentials extraction plug-in base interface""" + + def extract_credentials(self, request): + """Extract user credentials from given request + + Result of 'extract_credentials' call should be an ICredentials object for which + id is the 'raw' principal ID (without prefix); only authentication plug-ins should + add a prefix to principal IDs to distinguish principals + """ + + +class ICredentialsPlugin(ICredentialsInfo, IPlugin): + """Credentials extraction plug-in interface""" + + +class IAuthenticationInfo(Interface): + """Principal authentication plug-in base interface""" + + def authenticate(self, credentials, request): + """Authenticate given credentials and returns a principal ID or None""" + + +class IAuthenticationPlugin(IAuthenticationInfo, IPlugin): + """Principal authentication plug-in interface""" + + +class IAdminAuthenticationPlugin(IAuthenticationPlugin): + """Admin authentication plug-in base interface""" + + login = TextLine(title=_("Admin. login")) + + password = EncodedPassword(title=_("Admin. password")) + + +class IDirectoryInfo(Interface): + """Principal directory plug-in interface""" + + def get_principal(self, principal_id): + """Returns real principal matching given ID, or None""" + + def get_all_principals(self, principal_id): + """Returns all principals matching given principal ID""" + + def find_principals(self, query): + """Find principals matching given query""" + + +class IDirectoryPlugin(IDirectoryInfo, IPlugin): + """Principal directory plug-in info""" + + +class IDirectorySearchPlugin(IDirectoryPlugin, IContentSearch): + """Principal directory plug-in supporting search""" + + +class IUsersFolderPlugin(IAuthenticationPlugin, IDirectorySearchPlugin): + """Local users folder interface""" + + 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 + nbmin = 0 + nbn = 0 + nbo = 0 + for car in password: + if ord(car) in range(ord('A'), ord('Z') + 1): + nbmaj += 1 + elif ord(car) in range(ord('a'), ord('z') + 1): + nbmin += 1 + elif ord(car) in range(ord('0'), ord('9') + 1): + nbn += 1 + else: + nbo += 1 + if [nbmin, nbmaj, nbn, nbo].count(0) > 1: + raise Invalid(_("Your password must contain at least three of these kinds of characters: " + "lowercase letters, uppercase letters, numbers and special characters")) + + +EMAIL_REGEX = re.compile("[^@]+@[^@]+\.[^@]+") + + +class IUserRegistrationInfo(Interface): + """User registration info""" + + login = TextLine(title=_("User login"), + description=_("If you don't provide a custom login, your login will be your email address..."), + required=False) + + @invariant + def check_login(self): + if not self.login: + self.login = self.email + + email = TextLine(title=_("E-mail address"), + description=_("An email will be sent to this address to validate account activation; " + "it will be used as your future user login"), + required=True) + + @invariant + def check_email(self): + if not EMAIL_REGEX.match(self.email): + raise Invalid(_("Your email address is not valid!")) + + firstname = TextLine(title=_("First name"), + required=True) + + lastname = TextLine(title=_("Last name"), + required=True) + + company_name = TextLine(title=_("Company name"), + required=False) + + password = EncodedPassword(title=_("Password"), + description=_("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"), + min_length=8, + required=True) + + confirmed_password = EncodedPassword(title=_("Confirmed password"), + required=True) + + @invariant + def check_password(self): + if self.password != self.confirmed_password: + raise Invalid(_("You didn't confirmed your password correctly!")) + check_password(self.password) + + +class IUserRegistrationConfirmationInfo(Interface): + """User registration confirmation info""" + + activation_hash = TextLine(title=_("Activation hash"), + required=True) + + login = TextLine(title=_("User login"), + required=True) + + password = EncodedPassword(title=_("Password"), + min_length=8, + required=True) + + confirmed_password = EncodedPassword(title=_("Confirmed password"), + required=True) + + @invariant + def check_password(self): + if self.password != self.confirmed_password: + raise Invalid(_("You didn't confirmed your password correctly!")) + check_password(self.password) + + +class ILocalUser(IAttributeAnnotatable): + """Local user interface""" + + login = TextLine(title=_("User login"), + required=True, + readonly=True) + + @invariant + def check_login(self): + if not self.login: + self.login = self.email + + email = TextLine(title=_("User email address"), + required=True) + + @invariant + def check_email(self): + if not EMAIL_REGEX.match(self.email): + raise Invalid(_("Given email address is not valid!")) + + firstname = TextLine(title=_("First name"), + required=True) + + lastname = TextLine(title=_("Last name"), + required=True) + + title = Attribute("User full name") + + company_name = TextLine(title=_("Company name"), + required=False) + + password_manager = Choice(title=_("Password manager name"), + required=True, + vocabulary='PyAMS password managers', + default='SSHA') + + password = EncodedPassword(title=_("Password"), + min_length=8, + required=False) + + wait_confirmation = Bool(title=_("Wait confirmation?"), + description=_("If 'no', user will be activated immediately without waiting email " + "confirmation"), + required=True, + default=True) + + self_registered = Bool(title=_("Self-registered profile?"), + required=True, + default=True, + readonly=True) + + activation_secret = TextLine(title=_("Activation secret key"), + description=_("This private secret is used to create and check activation hash"), + readonly=True) + + activation_hash = TextLine(title=_("Activation hash"), + description=_("This hash is provided into activation message URL. Activation hash " + "is missing for local users which were registered without waiting " + "their confirmation."), + readonly=True) + + activation_date = Datetime(title=_("Activation date"), + required=False) + + activated = Bool(title=_("Activation date"), + required=True, + default=False) + + def check_password(self, password): + """Check user password against provided one""" + + def generate_secret(self, login, password): + """Generate secret key of this profile""" + + def check_activation(self, hash, login, password): + """Check activation for given settings""" + + +# +# Security manager +# + +class ISecurityManager(IContainer, IDirectoryInfo): + """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, + default=False) + + authomatic_secret = TextLine(title=_("Authomatic secret"), + description=_("This secret phrase is used to encrypt Authomatic cookie"), + default='this is not a secret', + required=True) + + social_login_use_popup = Bool(title=_("Use social popup?"), + required=True, + default=False) + + open_registration = Bool(title=_("Open registration?"), + description=_("If 'Yes', any use will be able to create a new user account"), + required=True, + default=False) + + users_folder = Choice(title=_("Users folder"), + description=_("Name of users folder used to store registered principals"), + required=False, + vocabulary='PyAMS users folders') + + @invariant + def check_users_folder(self): + if self.open_registration and not self.users_folder: + raise Invalid(_("You can't activate open registration without selecting a users folder")) + + credentials_plugins_names = Tuple(title=_("Credentials plug-ins"), + description=_("These plug-ins can be used to extract request credentials"), + value_type=TextLine(), + readonly=True, + default=()) + + authentication_plugins_names = Tuple(title=_("Authentication plug-ins"), + description=_("The plug-ins can be used to check extracted credentials " + "against a local or remote users database"), + value_type=TextLine(), + default=()) + + directory_plugins_names = Tuple(title=_("Directory plug-ins"), + description=_("The plug-in can be used to extract principals information"), + value_type=TextLine(), + default=()) + + def get_plugin(self, name): + """Get plug-in matching given name""" + + def get_credentials_plugins(self): + """Extract list of credentials plug-ins""" + + def order_credentials_plugins(self, names): + """Define credentials plug-ins order""" + + def get_authentication_plugins(self): + """Extract list of authentication plug-ins""" + + def order_authentication_plugins(self, names): + """Define authentication plug-ins order""" + + def get_directory_plugins(self): + """Extract list of directory plug-ins""" + + def order_directory_plugins(self, names): + """Define directory plug-ins order""" + + +LOGIN_REFERER_KEY = 'pyams_security.login.referer' + + +class ILoginView(Interface): + """Login view marker interface""" + + +# +# Social login configuration +# + +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"))) + + +class ISocialLoginProviderInfo(Interface): + """Social login provider info""" + + provider_name = TextLine(title=_("Provider name"), + 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']) diff -r 000000000000 -r f04e1d0a0723 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 000000000000 -r f04e1d0a0723 src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,511 @@ +# +# French translations for PACKAGE package +# This file is distributed under the same license as the PACKAGE package. +# Thierry Florac , 2015. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-02-19 00:29+0100\n" +"PO-Revision-Date: 2015-02-18 22:19+0100\n" +"Last-Translator: Thierry Florac \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Lingua 3.8\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/pyams_security/principal.py:49 +msgid "Not logged in" +msgstr "Non connecté" + +#: src/pyams_security/zmi/utility.py:73 +msgid "Security" +msgstr "Sécurité" + +#: src/pyams_security/zmi/utility.py:82 +msgid "Authentication and users directory plug-ins" +msgstr "Modules d'authentification et dossiers utilisateurs" + +#: src/pyams_security/zmi/utility.py:127 +msgid "Security manager" +msgstr "Gestionnaire de sécurité" + +#: src/pyams_security/zmi/utility.py:128 +msgid "Security manager plug-ins" +msgstr "Modules du gestionnaire de sécurité" + +#: src/pyams_security/zmi/utility.py:139 +msgid "Add..." +msgstr "Ajouter..." + +#: src/pyams_security/zmi/utility.py:147 +msgid "Properties..." +msgstr "Propriétés..." + +#: src/pyams_security/zmi/utility.py:160 +#: 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 +msgid "Security manager properties" +msgstr "Propriétés du gestionnaire de sécurité" + +#: src/pyams_security/zmi/plugin/admin.py:50 +msgid "Add admin authentication..." +msgstr "Ajouter un compte système..." + +#: src/pyams_security/zmi/plugin/admin.py:62 +msgid "Add administration authentication plug-in" +msgstr "Ajout d'un compte d'administration système" + +#: src/pyams_security/zmi/plugin/admin.py:95 +msgid "Edit administration authentication plug-in" +msgstr "Modification d'un compte d'administration système" + +#: src/pyams_security/zmi/plugin/admin.py:117 +msgid "WARNING" +msgstr "ATTENTION" + +#: src/pyams_security/zmi/plugin/admin.py:119 +msgid "" +"Before disabling plug-in, please verify that you have other administration " +"access!" +msgstr "" +"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/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/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/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/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/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/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/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/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 "" +"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" + +#: src/pyams_security/interfaces/__init__.py:396 +msgid "This secret phrase is used to encrypt Authomatic cookie" +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" + +#: 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/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/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/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:63 +msgid "Reset" +msgstr "Annuler" + +#: src/pyams_security/views/login.py:64 src/pyams_security/views/login.py:141 +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 "" +"Pas de gestionnaire de sécurité. Veuillez contacter l'administrateur du " +"site !" + +#: src/pyams_security/views/login.py:78 +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/views/userfolder.py:73 +msgid "User registration" +msgstr "Inscription" + +#: src/pyams_security/views/userfolder.py:74 +msgid "Please enter registration info" +msgstr "Veuillez indiquer les paramètres de votre compte" + +#: src/pyams_security/views/userfolder.py:155 +msgid "User registration confirmation" +msgstr "Confirmation d'inscription" + +#: 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/views/userfolder.py:66 +msgid "Register" +msgstr "Inscrire ce compte" + +#: src/pyams_security/views/userfolder.py:147 +msgid "Finalize registration" +msgstr "Terminer mon inscription" + +#: src/pyams_security/views/userfolder.py:119 +msgid "Can't create user profile. Please contact system administrator." +msgstr "" +"Impossible de créer votre compte utilisateur. Veuillez contacter " +"l'administrateur." + +#: src/pyams_security/views/userfolder.py:193 +msgid "Can't check user profile. Please contact system administrator." +msgstr "" +"Impossible de vérifier votre compte utilisateur. Veuillez contacter " +"l'administrateur." + +#: 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 +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/views/userfolder.py:198 +msgid "Can't retrieve user profile!" +msgstr "Impossible d'accéder à votre compte utilisateur !" + +#: 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 +msgid "Can't activate profile with given params!" +msgstr "" +"Impossible de confirmer votre inscription avec les paramètres indiqués !" diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po~ --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.po~ Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,511 @@ +# +# French translations for PACKAGE package +# This file is distributed under the same license as the PACKAGE package. +# Thierry Florac , 2015. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-02-18 23:59+0100\n" +"PO-Revision-Date: 2015-02-18 22:19+0100\n" +"Last-Translator: Thierry Florac \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Lingua 3.8\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/pyams_security/principal.py:49 +msgid "Not logged in" +msgstr "Non connecté" + +#: src/pyams_security/zmi/utility.py:73 +msgid "Security" +msgstr "Sécurité" + +#: src/pyams_security/zmi/utility.py:82 +msgid "Authentication and users directory plug-ins" +msgstr "Modules d'authentification et dossiers utilisateurs" + +#: src/pyams_security/zmi/utility.py:127 +msgid "Security manager" +msgstr "Gestionnaire de sécurité" + +#: src/pyams_security/zmi/utility.py:128 +msgid "Security manager plug-ins" +msgstr "Modules du gestionnaire de sécurité" + +#: src/pyams_security/zmi/utility.py:139 +msgid "Add..." +msgstr "Ajouter..." + +#: src/pyams_security/zmi/utility.py:147 +msgid "Properties..." +msgstr "Propriétés..." + +#: src/pyams_security/zmi/utility.py:160 +#: 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 +msgid "Security manager properties" +msgstr "Propriétés du gestionnaire de sécurité" + +#: src/pyams_security/zmi/plugin/admin.py:50 +msgid "Add admin authentication..." +msgstr "Ajouter un compte système..." + +#: src/pyams_security/zmi/plugin/admin.py:62 +msgid "Add administration authentication plug-in" +msgstr "Ajout d'un compte d'administration système" + +#: src/pyams_security/zmi/plugin/admin.py:95 +msgid "Edit administration authentication plug-in" +msgstr "Modification d'un compte d'administration système" + +#: src/pyams_security/zmi/plugin/admin.py:117 +msgid "WARNING" +msgstr "ATTENTION" + +#: src/pyams_security/zmi/plugin/admin.py:119 +msgid "" +"Before disabling plug-in, please verify that you have other administration " +"access!" +msgstr "" +"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 src/pyams_security/views/login.py:141 +msgid "Login" +msgstr "Code utilisateur" + +#: src/pyams_security/zmi/plugin/userfolder.py:190 +msgid "Name" +msgstr "Nom" + +#: src/pyams_security/zmi/plugin/userfolder.py:200 +#: src/pyams_security/interfaces/__init__.py:244 +msgid "E-mail address" +msgstr "Adresse de messagerie" + +#: src/pyams_security/zmi/plugin/userfolder.py:210 +msgid "Registration date" +msgstr "Date d'enregistrement" + +#: src/pyams_security/zmi/plugin/userfolder.py:226 +#: 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:242 +msgid "Add user" +msgstr "Ajouter un utilisateur" + +#: src/pyams_security/zmi/plugin/userfolder.py:255 +msgid "Add new local user" +msgstr "Ajout d'un utilisateur local" + +#: src/pyams_security/zmi/plugin/userfolder.py:326 +msgid "Edit user properties" +msgstr "Modification des propriétés d'un utilisateur" + +#: src/pyams_security/zmi/plugin/userfolder.py:304 +#: 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:315 +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/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/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/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/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/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/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 "" +"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" + +#: src/pyams_security/interfaces/__init__.py:396 +msgid "This secret phrase is used to encrypt Authomatic cookie" +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" + +#: 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/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/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/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:63 +msgid "Reset" +msgstr "Annuler" + +#: src/pyams_security/views/login.py:64 +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 "" +"Pas de gestionnaire de sécurité. Veuillez contacter l'administrateur du " +"site !" + +#: src/pyams_security/views/login.py:78 +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/views/userfolder.py:73 +msgid "User registration" +msgstr "Inscription" + +#: src/pyams_security/views/userfolder.py:74 +msgid "Please enter registration info" +msgstr "Veuillez indiquer les paramètres de votre compte" + +#: src/pyams_security/views/userfolder.py:155 +msgid "User registration confirmation" +msgstr "Confirmation d'inscription" + +#: 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/views/userfolder.py:66 +msgid "Register" +msgstr "Inscrire ce compte" + +#: src/pyams_security/views/userfolder.py:147 +msgid "Finalize registration" +msgstr "Terminer mon inscription" + +#: src/pyams_security/views/userfolder.py:119 +msgid "Can't create user profile. Please contact system administrator." +msgstr "" +"Impossible de créer votre compte utilisateur. Veuillez contacter " +"l'administrateur." + +#: src/pyams_security/views/userfolder.py:193 +msgid "Can't check user profile. Please contact system administrator." +msgstr "" +"Impossible de vérifier votre compte utilisateur. Veuillez contacter " +"l'administrateur." + +#: 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 +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/views/userfolder.py:198 +msgid "Can't retrieve user profile!" +msgstr "Impossible d'accéder à votre compte utilisateur !" + +#: 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 +msgid "Can't activate profile with given params!" +msgstr "" +"Impossible de confirmer votre inscription avec les paramètres indiqués !" diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/locales/pyams_security.pot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/locales/pyams_security.pot Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,471 @@ +# +# SOME DESCRIPTIVE TITLE +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2015. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-02-19 00:29+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Lingua 3.8\n" + +#: ./src/pyams_security/principal.py:49 +msgid "Not logged in" +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:73 +msgid "Security" +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:82 +msgid "Authentication and users directory plug-ins" +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:127 +msgid "Security manager" +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:128 +msgid "Security manager plug-ins" +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:139 +msgid "Add..." +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:147 +msgid "Properties..." +msgstr "" + +#: ./src/pyams_security/zmi/utility.py:160 +#: ./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 +msgid "Security manager properties" +msgstr "" + +#: ./src/pyams_security/zmi/plugin/admin.py:50 +msgid "Add admin authentication..." +msgstr "" + +#: ./src/pyams_security/zmi/plugin/admin.py:62 +msgid "Add administration authentication plug-in" +msgstr "" + +#: ./src/pyams_security/zmi/plugin/admin.py:95 +msgid "Edit administration authentication plug-in" +msgstr "" + +#: ./src/pyams_security/zmi/plugin/admin.py:117 +msgid "WARNING" +msgstr "" + +#: ./src/pyams_security/zmi/plugin/admin.py:119 +msgid "" +"Before disabling plug-in, please verify that you have other administration " +"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 "" + +#: ./src/pyams_security/views/userfolder.py:73 +msgid "User registration" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:74 +msgid "Please enter registration info" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:155 +msgid "User registration confirmation" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:156 +msgid "Please confirm your registration info" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:66 +msgid "Register" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:147 +msgid "Finalize registration" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:119 +msgid "Can't create user profile. Please contact system administrator." +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:193 +msgid "Can't check user profile. Please contact system administrator." +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:132 +msgid "Your registration is recorded!" +msgstr "" + +#: ./src/pyams_security/views/userfolder.py:134 +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 +msgid "Can't retrieve user profile!" +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 +msgid "Can't activate profile with given params!" +msgstr "" diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/permission.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/permission.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,50 @@ +# +# 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 IPermission + +# import packages +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(IPermission) +class Permission(object): + """Permission utility class""" + + id = FieldProperty(IPermission['id']) + title = FieldProperty(IPermission['title']) + description = FieldProperty(IPermission['description']) + + def __init__(self, values=None, **args): + if not isinstance(values, dict): + values = args + self.id = values.get('id') + self.title = values.get('title') + self.description = values.get('description') + + +def register_permission(config, permission): + """Register a new permission + + Permissions registry is not required. + But only registered permissions can be applied via default + ZMI features + """ + if not IPermission.providedBy(permission): + permission = Permission(permission) + config.registry.registerUtility(permission, IPermission, name=permission.id) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/plugin/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/plugin/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,41 @@ +# +# 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 + +# import packages + + +class PluginSelector(object): + """Plug-in based event selector + + This selector can be used by subscriber to filter authentication + events based on the name of the plug-in which fired the event. + """ + + def __init__(self, name, config): + self.plugin_name = name + + def text(self): + return 'plugin_selector = %s' % self.plugin_name + + phash = text + + def __call__(self, event): + if event.plugin == self.plugin_name: + return True + return False diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/plugin/admin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/plugin/admin.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,85 @@ +# +# 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 IAdminAuthenticationPlugin, IDirectoryPlugin +from zope.password.interfaces import IPasswordManager + +# import packages +from persistent import Persistent +from pyams_security.principal import PrincipalInfo +from pyams_utils.registry import get_utility +from zope.container.contained import Contained +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(IAdminAuthenticationPlugin, IDirectoryPlugin) +class AdminAuthenticationPlugin(Persistent, Contained): + """Hard-coded administrator authenticator plug-in + + This plug-in should only be enabled in development mode!!! + """ + + prefix = FieldProperty(IAdminAuthenticationPlugin['prefix']) + title = FieldProperty(IAdminAuthenticationPlugin['title']) + enabled = FieldProperty(IAdminAuthenticationPlugin['enabled']) + + login = FieldProperty(IAdminAuthenticationPlugin['login']) + _password = FieldProperty(IAdminAuthenticationPlugin['password']) + + @property + def password(self): + return self._password + + @password.setter + def password(self, value): + manager = get_utility(IPasswordManager, name='SSHA') + self._password = manager.encodePassword(value) + + def authenticate(self, credentials, request): + if not self.enabled: + return None + attrs = credentials.attributes + login = attrs.get('login') + password = attrs.get('password') + manager = get_utility(IPasswordManager, name='SSHA') + if login == self.login and manager.checkPassword(self._password, password): + return "{0}:{1}".format(self.prefix, login) + + def get_principal(self, principal_id): + if not self.enabled: + return None + prefix, login = principal_id.split(':', 1) + if (prefix == self.prefix) and (login == self.login): + return PrincipalInfo(id=principal_id, + title=self.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 query: + return None + query = query.lower() + if (query == self.login or + query in self.title.lower()): + yield PrincipalInfo(id='{0}:{1}'.format(self.prefix, self.login), + title=self.title) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/plugin/http.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/plugin/http.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,65 @@ +# +# 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 base64 + +# import interfaces +from pyams_security.interfaces import ICredentialsPlugin + +# import packages +from pyams_security.credential import Credentials +from pyams_utils.registry import utility_config +from pyams_utils.wsgi import wsgi_environ_cache + +from pyams_security import _ + + +ENVKEY_PARSED_CREDENTIALS = "pyams_security.http.basic.credentials" + + +@utility_config(name='http', provides=ICredentialsPlugin) +class HttpBasicCredentialsPlugin(object): + """HTTP basic credentials plug-in + + This credential plug-in is mainly used by automation processes using + XML-RPC or JSON-RPC requests launched from batch scripts. + + Copied from pyramid_httpauth package. + """ + + prefix = 'http' + title = _("HTTP Basic credentials") + enabled = True + + @wsgi_environ_cache(ENVKEY_PARSED_CREDENTIALS) + def extract_credentials(self, request, **kwargs): + """Extract login/password credentials from given request""" + auth = request.headers.get('Authorization') + if not auth: + return None + try: + scheme, params = auth.split(' ', 1) + if scheme.lower() != 'basic': + return None + token_bytes = base64.b64decode(params.strip()) + try: + token = token_bytes.decode('utf-8') + except UnicodeDecodeError: + token = token_bytes.decode('latin-1') + login, password = token.split(':', 1) + return Credentials(self.prefix, login, login=login, password=password) + except (ValueError, TypeError): + return None diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/plugin/userfolder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/plugin/userfolder.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,216 @@ +# +# 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 base64 +import hashlib +import hmac +import random +import sys +from datetime import datetime +from os import urandom + +# import interfaces +from pyams_security.interfaces import ISecurityManager, IUsersFolderPlugin, IAdminAuthenticationPlugin, ILocalUser, \ + IPrincipalInfo +from zope.lifecycleevent.interfaces import IObjectAddedEvent +from zope.password.interfaces import IPasswordManager +from zope.schema.interfaces import IVocabularyRegistry + +# import packages +from persistent import Persistent +from pyams_security.principal import PrincipalInfo +from pyams_utils.adapter import adapter_config +from pyams_utils.registry import query_utility, get_utility +from pyramid.events import subscriber +from zope.container.contained import Contained +from zope.container.folder import Folder +from zope.interface import implementer, provider, Invalid +from zope.schema.fieldproperty import FieldProperty +from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry + +from pyams_security import _ + + +@implementer(ILocalUser) +class User(Persistent, Contained): + """Local user persistent class""" + + login = FieldProperty(ILocalUser['login']) + email = FieldProperty(ILocalUser['email']) + firstname = FieldProperty(ILocalUser['firstname']) + lastname = FieldProperty(ILocalUser['lastname']) + company_name = FieldProperty(ILocalUser['company_name']) + _password = FieldProperty(ILocalUser['password']) + _password_salt = None + password_manager = FieldProperty(ILocalUser['password_manager']) + self_registered = FieldProperty(ILocalUser['self_registered']) + activation_secret = FieldProperty(ILocalUser['activation_secret']) + activation_hash = FieldProperty(ILocalUser['activation_hash']) + activation_date = FieldProperty(ILocalUser['activation_date']) + activated = FieldProperty(ILocalUser['activated']) + + @property + def title(self): + return '{0} {1}'.format(self.firstname, self.lastname) + + @property + def password(self): + return self._password + + @password.setter + def password(self, value): + self._password_salt = urandom(4) + manager = get_utility(IPasswordManager, name=self.password_manager) + if self.password_manager == 'Plain Text': + self._password = manager.encodePassword(value) + else: + self._password = manager.encodePassword(value, salt=self._password_salt) + + def check_password(self, password): + if not self.activated: + return False + manager = query_utility(IPasswordManager, name=self.password_manager) + if manager is None: + return False + return manager.checkPassword(self.password, password) + + def generate_secret(self): + seed = self.activation_secret = '-'.join((hex(random.randint(0, sys.maxsize))[2:] for i in range(5))) + secret = hmac.new(self.password, self.login.encode(), digestmod=hashlib.sha256) + secret.update(seed.encode()) + self.activation_hash = base64.b32encode(secret.digest()).decode() + + def check_activation(self, hash, login, password): + if self.self_registered: + # If principal was registered by it's own, we check activation hash + # with given login and password + manager = get_utility(IPasswordManager, name=self.password_manager) + password = manager.encodePassword(password, salt=self._password_salt) + secret = hmac.new(password, login.encode(), digestmod=hashlib.sha256) + secret.update(self.activation_secret.encode()) + activation_hash = base64.b32encode(secret.digest()).decode() + if hash != activation_hash: + raise Invalid(_("Can't activate profile with given params!")) + else: + # If principal was registered by a site manager, just check that + # hash is matching stored one and update user password... + if hash != self.activation_hash: + raise Invalid(_("Can't activate profile with given params!")) + self.password = password + self.activation_date = datetime.utcnow() + self.activated = True + + +@adapter_config(context=ILocalUser, provides=IPrincipalInfo) +def UserPrincipalInfoAdapter(user): + """User principal info adapter""" + return PrincipalInfo(id="{0}:{1}".format(user.__parent__.prefix, user.login), + title=user.title) + + +@implementer(IUsersFolderPlugin) +class UsersFolder(Folder): + """Local users folder""" + + prefix = FieldProperty(IAdminAuthenticationPlugin['prefix']) + title = FieldProperty(IAdminAuthenticationPlugin['title']) + enabled = FieldProperty(IAdminAuthenticationPlugin['enabled']) + + def authenticate(self, credentials, request): + if not self.enabled: + return None + attrs = credentials.attributes + login = attrs.get('login') + principal = self.get(login) + if principal is not None: + password = attrs.get('password') + if principal.check_password(password): + return "{0}:{1}".format(self.prefix, principal.login) + + def check_login(self, login): + if not login: + return False + return login 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, 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), + 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): + # TODO: use users 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()): + yield PrincipalInfo(id='{0}:{1}'.format(self.prefix, user.login), + title=user.title) + + def get_search_results(self, data): + # TODO: use users catalog for more efficient search? + query = data.get('query') + if not query: + return () + query = query.lower() + for user in self.values(): + if (query == user.login or + query in user.firstname.lower() or + query in user.lastname.lower()): + yield user + + +@provider(IVocabularyRegistry) +class UsersFolderVocabulary(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 IUsersFolderPlugin.providedBy(plugin): + terms.append(SimpleTerm(name, title=plugin.title)) + super(UsersFolderVocabulary, self).__init__(terms) + +getVocabularyRegistry().register('PyAMS users folders', UsersFolderVocabulary) + + +@subscriber(IObjectAddedEvent, context_selector=ILocalUser) +def handle_new_local_user(event): + """Send a confirmation message when a new user is recorded""" + user = event.object + if user.self_registered: + pass + else: + pass diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/principal.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/principal.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,72 @@ +# +# 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 IPrincipalInfo + +# import packages +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + +from pyams_security import _ + + +@implementer(IPrincipalInfo) +class PrincipalInfo(object): + """Generic principal info""" + + id = FieldProperty(IPrincipalInfo['id']) + title = FieldProperty(IPrincipalInfo['title']) + groups = FieldProperty(IPrincipalInfo['groups']) + + def __init__(self, **kwargs): + self.id = kwargs.get('id') + self.title = kwargs.get('title') + + def __eq__(self, other): + return isinstance(other, PrincipalInfo) and (self.id == other.id) + + +@implementer(IPrincipalInfo) +class UnknownPrincipal(object): + """Unknown principal info""" + + id = None + title = _("Not logged in") + +UnknownPrincipal = UnknownPrincipal() + + +@implementer(IPrincipalInfo) +class MissingPrincipal(object): + """Missing principal info + + This class can be used when a stored principal ID + references a principal which can't be found anymore + """ + + id = FieldProperty(IPrincipalInfo['id']) + + def __init__(self, **kwargs): + self.id = kwargs.get('id') + + @property + def title(self): + return ''.format(self.id) + + def __eq__(self, other): + return isinstance(other, PrincipalInfo) and (self.id == other.id) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/resources/js/authomatic.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/resources/js/authomatic.js Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,524 @@ +// Generated by CoffeeScript 1.6.2 +/* +# CoffeeDoc example documentation # + +This is a module-level docstring, and will be displayed at the top of the module documentation. +Documentation generated by [CoffeeDoc](http://github.com/omarkhan/coffeedoc) + +npm install -g coffeedoc +*/ + + +(function() { + var $, Authomatic, BaseProvider, Flickr, Foursquare, Google, LinkedIn, Oauth1Provider, Oauth2Provider, WindowsLive, deserializeCredentials, format, getProviderClass, globalOptions, jsonPCallbackCounter, log, openWindow, parseQueryString, parseUrl, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, + __slice = [].slice, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + $ = jQuery; + + jsonPCallbackCounter = 0; + + globalOptions = { + logging: true, + popupWidth: 800, + popupHeight: 600, + popupLinkSelector: 'a.authomatic', + popupFormSelector: 'form.authomatic', + popupFormValidator: function($form) { + return true; + }, + backend: null, + forceBackend: false, + substitute: {}, + params: {}, + headers: {}, + body: '', + jsonpCallbackPrefix: 'authomaticJsonpCallback', + onPopupInvalid: null, + onPopupOpen: null, + onLoginComplete: null, + onBackendStart: null, + onBackendComplete: null, + onAccessSuccess: null, + onAccessComplete: null + }; + + log = function() { + var args, _ref; + + args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + if (globalOptions.logging && ((typeof console !== "undefined" && console !== null ? (_ref = console.log) != null ? _ref.apply : void 0 : void 0) != null)) { + return typeof console !== "undefined" && console !== null ? console.log.apply(console, ['Authomatic:'].concat(__slice.call(args))) : void 0; + } + }; + + openWindow = function(url) { + var height, left, settings, top, width; + + width = globalOptions.popupWidth; + height = globalOptions.popupHeight; + top = (screen.height / 2) - (height / 2); + left = (screen.width / 2) - (width / 2); + settings = "width=" + width + ",height=" + height + ",top=" + top + ",left=" + left; + log('Opening popup:', url); + if (typeof globalOptions.onPopupOpen === "function") { + globalOptions.onPopupOpen(url); + } + return window.open(url, '', settings); + }; + + parseQueryString = function(queryString) { + var item, k, result, v, _i, _len, _ref, _ref1; + + result = {}; + _ref = queryString.split('&'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + item = _ref[_i]; + _ref1 = item.split('='), k = _ref1[0], v = _ref1[1]; + v = decodeURIComponent(v); + if (result.hasOwnProperty(k)) { + if (Array.isArray(result[k])) { + result[k].push(v); + } else { + result[k] = [result[k], v]; + } + } else { + result[k] = v; + } + } + return result; + }; + + parseUrl = function(url) { + var qs, questionmarkIndex, u; + + log('parseUrl', url); + questionmarkIndex = url.indexOf('?'); + if (questionmarkIndex >= 0) { + u = url.substring(0, questionmarkIndex); + qs = url.substring(questionmarkIndex + 1); + } else { + u = url; + } + return { + url: u, + query: qs, + params: qs ? parseQueryString(qs) : void 0 + }; + }; + + deserializeCredentials = function(credentials) { + var sc, subtype, type, typeId, _ref; + + sc = decodeURIComponent(credentials).split('\n'); + typeId = sc[1]; + _ref = typeId.split('-'), type = _ref[0], subtype = _ref[1]; + return { + id: parseInt(sc[0]), + typeId: typeId, + type: parseInt(type), + subtype: parseInt(subtype), + rest: sc.slice(2) + }; + }; + + getProviderClass = function(credentials) { + var subtype, type, _ref; + + _ref = deserializeCredentials(credentials), type = _ref.type, subtype = _ref.subtype; + if (type === 1) { + if (subtype === 2) { + return Flickr; + } else { + return Oauth1Provider; + } + } else if (type === 2) { + if (subtype === 6) { + return Foursquare; + } else if (subtype === 9) { + return LinkedIn; + } else if (subtype === 14) { + return WindowsLive; + } else if (subtype === 12 || subtype === 15) { + return BaseProvider; + } else { + return Oauth2Provider; + } + } else { + return BaseProvider; + } + }; + + format = function(template, substitute) { + return template.replace(/{([^}]*)}/g, function(match, tag) { + var level, target, _i, _len, _ref; + + target = substitute; + _ref = tag.split('.'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + level = _ref[_i]; + target = target[level]; + } + return target; + }); + }; + + window.authomatic = new (Authomatic = (function() { + function Authomatic() {} + + Authomatic.prototype.setup = function(options) { + $.extend(globalOptions, options); + return log('Setting up authomatic to:', globalOptions); + }; + + Authomatic.prototype.popupInit = function() { + $(globalOptions.popupLinkSelector).click(function(e) { + e.preventDefault(); + return openWindow($(this).attr('href')); + }); + return $(globalOptions.popupFormSelector).submit(function(e) { + var $form, url; + + e.preventDefault(); + $form = $(this); + url = $form.attr('action') + '?' + $form.serialize(); + if (globalOptions.popupFormValidator($form)) { + return openWindow(url); + } else { + return typeof globalOptions.onPopupInvalid === "function" ? globalOptions.onPopupInvalid($form) : void 0; + } + }); + }; + + Authomatic.prototype.loginComplete = function(result, closer) { + var result_copy; + + result_copy = $.extend(true, {}, result); + log('Login procedure complete', result_copy); + closer(); + return globalOptions.onLoginComplete(result_copy); + }; + + Authomatic.prototype.access = function(credentials, url, options) { + var Provider, localEvents, provider, updatedOptions; + + if (options == null) { + options = {}; + } + localEvents = { + onBackendStart: null, + onBackendComplete: null, + onAccessSuccess: null, + onAccessComplete: null + }; + updatedOptions = {}; + $.extend(updatedOptions, globalOptions, localEvents, options); + url = format(url, updatedOptions.substitute); + log('access options', updatedOptions, globalOptions); + if (updatedOptions.forceBackend) { + Provider = BaseProvider; + } else { + Provider = getProviderClass(credentials); + } + provider = new Provider(options.backend, credentials, url, updatedOptions); + log('Instantiating provider:', provider); + return provider.access(); + }; + + return Authomatic; + + })()); + + BaseProvider = (function() { + BaseProvider.prototype._x_jsonpCallbackParamName = 'callback'; + + function BaseProvider(backend, credentials, url, options) { + var parsedUrl; + + this.backend = backend; + this.credentials = credentials; + this.options = options; + this.access = __bind(this.access, this); + this.contactProvider = __bind(this.contactProvider, this); + this.contactBackend = __bind(this.contactBackend, this); + this.backendRequestType = 'auto'; + this.jsonpRequest = false; + this.jsonpCallbackName = "" + globalOptions.jsonpCallbackPrefix + jsonPCallbackCounter; + this.deserializedCredentials = deserializeCredentials(this.credentials); + this.providerID = this.deserializedCredentials.id; + this.providerType = this.deserializedCredentials.type; + this.credentialsRest = this.deserializedCredentials.rest; + parsedUrl = parseUrl(url); + this.url = parsedUrl.url; + this.params = {}; + $.extend(this.params, parsedUrl.params, this.options.params); + } + + BaseProvider.prototype.contactBackend = function(callback) { + var data, _base; + + if (this.jsonpRequest && this.options.method === !'GET') { + this.backendRequestType = 'fetch'; + } + data = { + type: this.backendRequestType, + credentials: this.credentials, + url: this.url, + method: this.options.method, + body: this.options.body, + params: JSON.stringify(this.params), + headers: JSON.stringify(this.options.headers) + }; + if (typeof globalOptions.onBackendStart === "function") { + globalOptions.onBackendStart(data); + } + if (typeof (_base = this.options).onBackendStart === "function") { + _base.onBackendStart(data); + } + log("Contacting backend at " + this.options.backend + ".", data); + return $.get(this.options.backend, data, callback); + }; + + BaseProvider.prototype.contactProvider = function(requestElements) { + var body, headers, jsonpOptions, method, options, params, url, + _this = this; + + url = requestElements.url, method = requestElements.method, params = requestElements.params, headers = requestElements.headers, body = requestElements.body; + options = { + type: method, + data: params, + headers: headers, + complete: [ + (function(jqXHR, textStatus) { + return log('Request complete.', textStatus, jqXHR); + }), globalOptions.onAccessComplete, this.options.onAccessComplete + ], + success: [ + (function(data) { + return log('Request successful.', data); + }), globalOptions.onAccessSuccess, this.options.onAccessSuccess + ], + error: function(jqXHR, textStatus, errorThrown) { + if (jqXHR.state() === 'rejected') { + if (_this.options.method === 'GET') { + log('Cross domain request failed! trying JSONP request.'); + _this.jsonpRequest = true; + } else { + _this.backendRequestType = 'fetch'; + } + return _this.access(); + } + } + }; + if (this.jsonpRequest) { + jsonpOptions = { + jsonpCallback: this.jsonpCallbackName, + jsonp: this._x_jsonpCallbackParamName, + cache: true, + dataType: 'jsonp', + error: function(jqXHR, textStatus, errorThrown) { + return log('JSONP failed! State:', jqXHR.state()); + } + }; + $.extend(options, jsonpOptions); + log("Contacting provider with JSONP request.", url, options); + } else { + log("Contacting provider with cross domain request", url, options); + } + return $.ajax(url, options); + }; + + BaseProvider.prototype.access = function() { + var callback, + _this = this; + + callback = function(data, textStatus, jqXHR) { + var responseTo, _base, _base1, _base2; + + if (typeof globalOptions.onBackendComplete === "function") { + globalOptions.onBackendComplete(data, textStatus, jqXHR); + } + if (typeof (_base = _this.options).onBackendComplete === "function") { + _base.onBackendComplete(data, textStatus, jqXHR); + } + responseTo = jqXHR != null ? jqXHR.getResponseHeader('Authomatic-Response-To') : void 0; + if (responseTo === 'fetch') { + log("Fetch data returned from backend.", jqXHR.getResponseHeader('content-type'), data); + if (typeof globalOptions.onAccessSuccess === "function") { + globalOptions.onAccessSuccess(data); + } + if (typeof (_base1 = _this.options).onAccessSuccess === "function") { + _base1.onAccessSuccess(data); + } + if (typeof globalOptions.onAccessComplete === "function") { + globalOptions.onAccessComplete(jqXHR, textStatus); + } + return typeof (_base2 = _this.options).onAccessComplete === "function" ? _base2.onAccessComplete(jqXHR, textStatus) : void 0; + } else if (responseTo === 'elements') { + log("Request elements data returned from backend.", data); + return _this.contactProvider(data); + } + }; + if (this.jsonpRequest) { + jsonPCallbackCounter += 1; + } + return this.contactBackend(callback); + }; + + return BaseProvider; + + })(); + + Oauth1Provider = (function(_super) { + __extends(Oauth1Provider, _super); + + function Oauth1Provider() { + this.contactProvider = __bind(this.contactProvider, this); + this.access = __bind(this.access, this); _ref = Oauth1Provider.__super__.constructor.apply(this, arguments); + return _ref; + } + + Oauth1Provider.prototype.access = function() { + this.jsonpRequest = true; + this.params[this._x_jsonpCallbackParamName] = this.jsonpCallbackName; + return Oauth1Provider.__super__.access.call(this); + }; + + Oauth1Provider.prototype.contactProvider = function(requestElements) { + delete requestElements.params.callback; + return Oauth1Provider.__super__.contactProvider.call(this, requestElements); + }; + + return Oauth1Provider; + + })(BaseProvider); + + Flickr = (function(_super) { + __extends(Flickr, _super); + + function Flickr() { + _ref1 = Flickr.__super__.constructor.apply(this, arguments); + return _ref1; + } + + Flickr.prototype._x_jsonpCallbackParamName = 'jsoncallback'; + + return Flickr; + + })(Oauth1Provider); + + Oauth2Provider = (function(_super) { + __extends(Oauth2Provider, _super); + + Oauth2Provider.prototype._x_accessToken = 'access_token'; + + Oauth2Provider.prototype._x_bearer = 'Bearer'; + + function Oauth2Provider() { + var args, _ref2; + + args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + this.access = __bind(this.access, this); + this.handleTokenType = __bind(this.handleTokenType, this); + Oauth2Provider.__super__.constructor.apply(this, args); + _ref2 = this.credentialsRest, this.accessToken = _ref2[0], this.refreshToken = _ref2[1], this.expirationTime = _ref2[2], this.tokenType = _ref2[3]; + this.handleTokenType(); + } + + Oauth2Provider.prototype.handleTokenType = function() { + if (this.tokenType === '1') { + return this.options.headers['Authorization'] = "" + this._x_bearer + " " + this.accessToken; + } else { + return this.params[this._x_accessToken] = this.accessToken; + } + }; + + Oauth2Provider.prototype.access = function() { + var requestElements; + + if (this.backendRequestType === 'fetch') { + return Oauth2Provider.__super__.access.call(this); + } else { + requestElements = { + url: this.url, + method: this.options.method, + params: this.params, + headers: this.options.headers, + body: this.options.body + }; + return this.contactProvider(requestElements); + } + }; + + return Oauth2Provider; + + })(BaseProvider); + + Foursquare = (function(_super) { + __extends(Foursquare, _super); + + function Foursquare() { + _ref2 = Foursquare.__super__.constructor.apply(this, arguments); + return _ref2; + } + + Foursquare.prototype._x_accessToken = 'oauth_token'; + + return Foursquare; + + })(Oauth2Provider); + + Google = (function(_super) { + __extends(Google, _super); + + function Google() { + _ref3 = Google.__super__.constructor.apply(this, arguments); + return _ref3; + } + + Google.prototype._x_bearer = 'OAuth'; + + return Google; + + })(Oauth2Provider); + + LinkedIn = (function(_super) { + __extends(LinkedIn, _super); + + function LinkedIn() { + _ref4 = LinkedIn.__super__.constructor.apply(this, arguments); + return _ref4; + } + + LinkedIn.prototype._x_accessToken = 'oauth2_access_token'; + + return LinkedIn; + + })(Oauth2Provider); + + WindowsLive = (function(_super) { + __extends(WindowsLive, _super); + + function WindowsLive() { + this.handleTokenType = __bind(this.handleTokenType, this); _ref5 = WindowsLive.__super__.constructor.apply(this, arguments); + return _ref5; + } + + WindowsLive.prototype.handleTokenType = function() { + if (this.tokenType === '1') { + this.options.headers['Authorization'] = "" + this._x_bearer + " " + this.accessToken; + } + return this.params[this._x_accessToken] = this.accessToken; + }; + + return WindowsLive; + + })(Oauth2Provider); + +}).call(this); + +/* +//@ sourceMappingURL=authomatic.map +*/ diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/resources/js/authomatic.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/resources/js/authomatic.min.js Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,1 @@ +(function(){var l,k,z,C,w,u,b,t,q,d,s,y,g,c,o,m,A,x,v,n,i,h,f,e,a,r=[].slice,j=function(D,E){return function(){return D.apply(E,arguments)}},p={}.hasOwnProperty,B=function(G,E){for(var D in E){if(p.call(E,D)){G[D]=E[D]}}function F(){this.constructor=G}F.prototype=E.prototype;G.prototype=new F();G.__super__=E.prototype;return G};l=jQuery;o=0;c={logging:true,popupWidth:800,popupHeight:600,popupLinkSelector:"a.authomatic",popupFormSelector:"form.authomatic",popupFormValidator:function(D){return true},backend:null,forceBackend:false,substitute:{},params:{},headers:{},body:"",jsonpCallbackPrefix:"authomaticJsonpCallback",onPopupInvalid:null,onPopupOpen:null,onLoginComplete:null,onBackendStart:null,onBackendComplete:null,onAccessSuccess:null,onAccessComplete:null};m=function(){var D,E;D=1<=arguments.length?r.call(arguments,0):[];if(c.logging&&((typeof console!=="undefined"&&console!==null?(E=console.log)!=null?E.apply:void 0:void 0)!=null)){return typeof console!=="undefined"&&console!==null?console.log.apply(console,["Authomatic:"].concat(r.call(D))):void 0}};A=function(E){var D,I,G,H,F;F=c.popupWidth;D=c.popupHeight;H=(screen.height/2)-(D/2);I=(screen.width/2)-(F/2);G="width="+F+",height="+D+",top="+H+",left="+I;m("Opening popup:",E);if(typeof c.onPopupOpen==="function"){c.onPopupOpen(E)}return window.open(E,"",G)};x=function(D){var K,F,L,J,G,I,H,E;L={};H=D.split("&");for(G=0,I=H.length;G=0){F=G.substring(0,D);E=G.substring(D+1)}else{F=G}return{url:F,query:E,params:E?x(E):void 0}};s=function(E){var I,D,G,F,H;I=decodeURIComponent(E).split("\n");F=I[1];H=F.split("-"),G=H[0],D=H[1];return{id:parseInt(I[0]),typeId:F,type:parseInt(G),subtype:parseInt(D),rest:I.slice(2)}};g=function(E){var D,F,G;G=s(E),F=G.type,D=G.subtype;if(F===1){if(D===2){return C}else{return t}}else{if(F===2){if(D===6){return w}else{if(D===9){return b}else{if(D===14){return d}else{if(D===12||D===15){return z}else{return q}}}}}else{return z}}};y=function(E,D){return E.replace(/{([^}]*)}/g,function(H,F){var L,K,J,G,I;K=D;I=F.split(".");for(J=0,G=I.length;J +# 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 + +# import packages +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(IRole) +class Role(object): + """Role utility class""" + + id = FieldProperty(IRole['id']) + title = FieldProperty(IRole['title']) + description = FieldProperty(IRole['description']) + permissions = FieldProperty(IRole['permissions']) + + def __init__(self, values=None, **args): + if not isinstance(values, dict): + values = args + self.id = values.get('id') + self.title = values.get('title') + self.description = values.get('description') + self.permissions = values.get('permissions') + + +def register_role(config, role): + """Register a new role + + Roles registry is not required. + But only registered roles can be applied via default + ZMI features + """ + if not IRole.providedBy(role): + role = Role(role) + config.registry.registerUtility(role, IRole, name=role.id) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/site.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/site.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,66 @@ +# +# 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_utils.interfaces.site import ISiteGenerations +from zope.lifecycleevent.interfaces import IObjectCreatedEvent +from zope.site.interfaces import INewLocalSite + +# import packages +from pyams_security.plugin.admin import AdminAuthenticationPlugin +from pyams_security.utility import SecurityManager +from pyams_utils.registry import utility_config +from pyams_utils.request import check_request +from pyams_utils.site import check_required_utilities +from pyramid.events import subscriber +from zope.lifecycleevent import ObjectCreatedEvent + + +REQUIRED_UTILITIES = ((ISecurityManager, '', SecurityManager, 'Security manager'),) + + +@subscriber(INewLocalSite) +def handle_new_local_site(event): + """Create a new negotiator when a site is created""" + site = event.manager.__parent__ + check_required_utilities(site, REQUIRED_UTILITIES) + + +@utility_config(name='PyAMS security', provides=ISiteGenerations) +class SecurityGenerationsChecker(object): + """I18n generations checker""" + + generation = 1 + + def evolve(self, site, current=None): + """Check for required utilities""" + check_required_utilities(site, REQUIRED_UTILITIES) + + +@subscriber(IObjectCreatedEvent, context_selector=ISecurityManager) +def handle_new_security_manager(event): + """Automatically create a new administration login""" + admin_auth = AdminAuthenticationPlugin() + admin_auth.prefix = 'system' + admin_auth.title = 'System manager authentication' + admin_auth.login = 'admin' + admin_auth.password = 'admin' + request = check_request() + request.registry.notify(ObjectCreatedEvent(admin_auth)) + utility = event.object + utility['__system__'] = admin_auth diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/tests/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/tests/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/tests/test_utilsdocs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/tests/test_utilsdocs.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,59 @@ +# +# 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. +# + +""" +Generic Test case for pyams_security doctest +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +current_dir = os.path.dirname(__file__) + +def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): + """Returns a test suite, based on doctests found in /doctest.""" + suite = [] + if globs is None: + globs = globals() + + flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | + doctest.REPORT_ONLY_FIRST_FAILURE) + + package_dir = os.path.split(test_dir)[0] + if package_dir not in sys.path: + sys.path.append(package_dir) + + doctest_dir = os.path.join(package_dir, 'doctests') + + # filtering files on extension + docs = [os.path.join(doctest_dir, doc) for doc in + os.listdir(doctest_dir) if doc.endswith('.txt')] + + for test in docs: + suite.append(doctest.DocFileSuite(test, optionflags=flags, + globs=globs, setUp=setUp, + tearDown=tearDown, + module_relative=False)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/tests/test_utilsdocstrings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/tests/test_utilsdocstrings.py Thu Feb 19 10:53:29 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. +# + +""" +Generic Test case for pyams_security doc strings +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +current_dir = os.path.abspath(os.path.dirname(__file__)) + +def doc_suite(test_dir, globs=None): + """Returns a test suite, based on doc tests strings found in /*.py""" + suite = [] + if globs is None: + globs = globals() + + flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | + doctest.REPORT_ONLY_FIRST_FAILURE) + + package_dir = os.path.split(test_dir)[0] + if package_dir not in sys.path: + sys.path.append(package_dir) + + # filtering files on extension + docs = [doc for doc in + os.listdir(package_dir) if doc.endswith('.py')] + docs = [doc for doc in docs if not doc.startswith('__')] + + for test in docs: + fd = open(os.path.join(package_dir, test)) + content = fd.read() + fd.close() + if '>>> ' not in content: + continue + test = test.replace('.py', '') + location = 'pyams_security.%s' % test + suite.append(doctest.DocTestSuite(location, optionflags=flags, + globs=globs)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/utility.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/utility.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,237 @@ +# +# 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, ICredentialsPlugin, IAuthenticationPlugin, \ + IDirectoryPlugin, AuthenticatedPrincipalEvent +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 pyramid.authentication import AuthTktCookieHelper +from pyramid.decorator import reify +from pyramid.security import Everyone, Authenticated +from zope.container.folder import Folder +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(ISecurityManager) +class SecurityManager(Folder): + """Security manager utility""" + + enable_social_login = FieldProperty(ISecurityManager['enable_social_login']) + authomatic_secret = FieldProperty(ISecurityManager['authomatic_secret']) + social_login_use_popup = FieldProperty(ISecurityManager['social_login_use_popup']) + open_registration = FieldProperty(ISecurityManager['open_registration']) + users_folder = FieldProperty(ISecurityManager['users_folder']) + + authentication_plugins_names = FieldProperty(ISecurityManager['authentication_plugins_names']) + directory_plugins_names = FieldProperty(ISecurityManager['directory_plugins_names']) + + @property + def credentials_plugins_names(self): + request = check_request() + policy = request.registry.queryUtility(IAuthenticationPolicy) + return policy.credentials_names + + def __setitem__(self, key, value): + super(SecurityManager, self).__setitem__(key, value) + if IAuthenticationPlugin.providedBy(value): + self.authentication_plugins_names += (key,) + if IDirectoryPlugin.providedBy(value): + self.directory_plugins_names += (key,) + + 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)] + if key in self.directory_plugins_names: + del self.directory_plugins_names[self.directory_plugins_names.index(key)] + + def get_plugin(self, name): + if name in self.credentials_plugins_names: + return query_utility(ICredentialsPlugin, name=name) + elif name: + return self.get(name) + + def get_credentials_plugins(self, request=None): + if request is None: + request = check_request() + policy = request.registry.queryUtility(IAuthenticationPolicy) + for plugin in policy.credentials_plugins: + if plugin is not None: + yield plugin + + def get_authentication_plugins(self): + for name in self.authentication_plugins_names or (): + plugin = self.get(name) + if IAuthenticationPlugin.providedBy(plugin): + yield plugin + + def get_directory_plugins(self): + for name in self.directory_plugins_names or (): + plugin = self.get(name) + if IDirectoryPlugin.providedBy(plugin): + yield plugin + + # IAuthenticationInfo interface methods + def extract_credentials(self, request, **kwargs): + for plugin in self.get_credentials_plugins(): + credentials = plugin.extract_credentials(request, **kwargs) + if credentials: + return credentials + + def authenticate(self, credentials, request): + for plugin in self.get_authentication_plugins(): + principal_id = plugin.authenticate(credentials, request) + if principal_id is not None: + request.registry.notify(AuthenticatedPrincipalEvent(plugin.prefix, principal_id)) + return principal_id + + def authenticated_userid(self, request): + credentials = self.extract_credentials(request) + if credentials is None: + return None + principal = self.authenticate(credentials, request) + if principal is not None: + principal = self.get_principal(principal.id) + if principal is not None: + return principal.id + + def effective_principals(self, principal_id): + principals = set() + for plugin in self.get_directory_plugins(): + principals |= set(plugin.get_all_principals(principal_id)) + return principals + + # IDirectoryPlugin interface methods + def get_principal(self, principal_id): + if not principal_id: + return UnknownPrincipal + for plugin in self.get_directory_plugins(): + principal = plugin.get_principal(principal_id) + if principal is not None: + return principal + return MissingPrincipal(id=principal_id) + + def find_principals(self, query): + principals = set() + for plugin in self.get_directory_plugins(): + principals |= set(plugin.find_principals(query) or set()) + return sorted(principals, key=lambda x: x.title) + + +@implementer(IAuthenticationPolicy) +class PyAMSAuthenticationPolicy(object): + """PyAMS authentication policy + + This authentication policy relies on a registered ISecurityManager utility. + Use same authentication ticket as AuthTktAuthenticationPolicy. + + ``credentials`` is the list of credentials extraction utilities which can be + used to get credentials. + + See `pyramid.authentication.AuthTktAuthenticationPolicy` to get description + of other constructor arguments. + """ + + def __init__(self, secret, + credentials=('http', 'session'), + cookie_name='auth_ticket', + secure=False, + include_ip=False, + timeout=None, + reissue_time=None, + max_age=None, + path="/", + http_only=False, + wild_domain=True, + hashalg='sha256', + parent_domain=False, + domain=None): + self.credentials_names = credentials + self.cookie = AuthTktCookieHelper(secret, + cookie_name=cookie_name, + secure=secure, + include_ip=include_ip, + timeout=timeout, + reissue_time=reissue_time, + max_age=max_age, + http_only=http_only, + path=path, + wild_domain=wild_domain, + hashalg=hashalg, + parent_domain=parent_domain, + domain=domain) + + @reify + def credentials_plugins(self): + return [query_utility(ICredentialsPlugin, name=name) + for name in self.credentials_names] + + def _get_security_manager(self, request): + return query_utility(ISecurityManager) + + def unauthenticated_userid(self, request): + result = self.cookie.identify(request) + if result: + return result['userid'] + for plugin in self.credentials_plugins: + if plugin is not None: + credentials = plugin.extract_credentials(request) + if credentials is not None: + return credentials.id + + def authenticated_userid(self, request): + principal_id = self.unauthenticated_userid(request) + if principal_id: + return principal_id + manager = self._get_security_manager(request) + if manager is not None: + return manager.authenticated_userid(request) + + def effective_principals(self, request): + principals = {Everyone} + principal_id = self.unauthenticated_userid(request) + if principal_id: + principals.add(Authenticated) + principals.add(principal_id) + manager = self._get_security_manager(request) + if manager is not None: + principals |= set(manager.effective_principals(principal_id)) + print(principals) + return principals + + def remember(self, request, principal, **kw): + return self.cookie.remember(request, principal, **kw) + + def forget(self, request): + return self.cookie.forget(request) + + +def get_principal(request): + """Get principal associated with given request""" + manager = query_utility(ISecurityManager) + if manager is not None: + principal_id = request.authenticated_userid + if principal_id: + return manager.get_principal(principal_id) + else: + return UnknownPrincipal diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,19 @@ +# +# 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 + +# import packages diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/login.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/login.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,201 @@ +# +# 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 ILoginView, ISecurityManager, LOGIN_REFERER_KEY +from pyams_skin.interfaces import IModalFullPage +from pyams_skin.layer import IPyAMSLayer +from z3c.form.interfaces import IDataExtractedEvent + +# import packages +from pyams_form.form import AddForm, AJAXAddForm, DialogAddForm +from pyams_form.schema import ResetButton, CloseButton +from pyams_pagelet.pagelet import pagelet_config +from pyams_security.credential import Credentials +from pyams_utils.registry import query_utility +from pyramid.events import subscriber +from pyramid.httpexceptions import HTTPFound +from pyramid.response import Response +from pyramid.security import remember, forget +from pyramid.view import view_config, forbidden_view_config +from z3c.form import field, button +from zope.interface import implementer, Interface, Invalid +from zope.schema import TextLine, Password + +from pyams_security import _ + + +# +# Login views +# + +@forbidden_view_config(request_type=IPyAMSLayer) +def ForbiddenView(request): + """Default forbidden view""" + request.session[LOGIN_REFERER_KEY] = request.view_name + return HTTPFound('login.html') + + +class ILoginFormFields(Interface): + """Login form fields""" + + login = TextLine(title=_("Login")) + password = Password(title=_("Password")) + + +class ILoginFormButtons(Interface): + """Login form buttons""" + + reset = ResetButton(name='reset', title=_("Reset")) + login = button.Button(name='login', title=_("Connect")) + + +@subscriber(IDataExtractedEvent, form_selector=ILoginView) +def handle_login_form_data(event): + """Check credentials after data extraction""" + data = event.data + if 'principal_id' in data: + del data['principal_id'] + manager = query_utility(ISecurityManager) + if manager is not None: + credentials = Credentials('form', id=data['login'], **data) + principal_id = manager.authenticate(credentials, event.form.request) + if principal_id is None: + event.form.widgets.errors += (Invalid(_("Invalid credentials!")),) + else: + data['principal_id'] = principal_id + else: + event.form.widgets.errors += (Invalid(_("Missing security manager utility. Please contact administrator!")),) + + +@pagelet_config(name='login.html', layer=IPyAMSLayer) +@implementer(IModalFullPage, ILoginView) +class LoginForm(AddForm): + """Login form""" + + legend = _("Please enter valid credentials to log-in") + fields = field.Fields(ILoginFormFields) + buttons = button.Buttons(ILoginFormButtons) + ajax_handler = 'login.json' + edit_permission = None + + def updateActions(self): + super(LoginForm, self).updateActions() + if 'login' in self.actions: + self.actions['login'].addClass('btn-primary') + + def createAndAdd(self, data): + principal_id = data.get('principal_id') + if principal_id is not None: + headers = remember(self.request, principal_id) + response = self.request.response + response.headerlist.extend(headers) + if not self.request.is_xhr: + response.status_code = 302 + session = self.request.session + if LOGIN_REFERER_KEY in session: + response.location = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + else: + response.location = '/' + + +@view_config(name='login.json', request_type=IPyAMSLayer, renderer='json', xhr=True) +class LoginAJAXForm(AJAXAddForm, LoginForm): + """Login form, AJAX view""" + + def get_ajax_output(self, changes): + status = {'status': 'redirect'} + session = self.request.session + if LOGIN_REFERER_KEY in session: + status['location'] = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + return status + + +@forbidden_view_config(request_type=IPyAMSLayer, renderer='json', xhr=True) +def ForbiddenAJAXView(request): + """AJAX call forbidden view""" + return {'status': 'modal', + 'location': 'login-dialog.html'} + + +class ILoginDialogFormButtons(Interface): + """Login dialog form buttons""" + + close = CloseButton(name='close', title=_("Cancel")) + login = button.Button(name='login', title=_("Connect")) + + +@pagelet_config(name='login-dialog.html', layer=IPyAMSLayer) +@implementer(ILoginView) +class LoginDialogForm(DialogAddForm): + """Login dialog form""" + + title = _("Please enter valid credentials to log-in") + legend = None + fields = field.Fields(ILoginFormFields) + buttons = button.Buttons(ILoginDialogFormButtons) + ajax_handler = 'login-dialog.json' + edit_permission = None + + def update(self): + super(LoginDialogForm, self).update() + self.request.session[LOGIN_REFERER_KEY] = self.request.referer + + def updateActions(self): + super(LoginDialogForm, self).updateActions() + if 'login' in self.actions: + self.actions['login'].addClass('btn-primary') + + def createAndAdd(self, data): + credentials = Credentials('form', id=data['login'], **data) + manager = query_utility(ISecurityManager) + if manager is not None: + principal_id = manager.authenticate(credentials, self.request) + if principal_id is not None: + headers = remember(self.request, principal_id) + response = self.request.response + response.headerlist.extend(headers) + + +@view_config(name='login-dialog.json', request_type=IPyAMSLayer, renderer='json', xhr=True) +class LoginDialogAJAXForm(AJAXAddForm, LoginDialogForm): + """Login dialog form, AJAX view""" + + def get_ajax_output(self, changes): + status = {'status': 'redirect'} + session = self.request.session + if LOGIN_REFERER_KEY in session: + status['location'] = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + return status + + +# +# Logout view +# + +@view_config(name='logout.html', request_type=IPyAMSLayer) +def logout(request): + """Logout view""" + headers = forget(request) + response = Response() + response.headerlist.extend(headers) + response.status_code = 302 + response.location = request.referer or '/' + return response diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/oauth.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/oauth.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,133 @@ +# +# 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 logging import WARNING + +# 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 + +# 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 +from pyramid.httpexceptions import HTTPNotFound +from pyramid.response import Response +from pyramid.security import remember +from pyramid.view import view_config + + +# +# Authomatic social login +# + +@viewlet_config(name='social-login-suffix', layer=IPyAMSLayer, + manager=IWidgetsSuffixViewletsManager, view=ILoginView, weight=50) +@template_config(template='templates/social-login.pt') +class SocialLoginViewletsSuffix(Viewlet): + """Social login viewlets suffix""" + + def __new__(cls, *args, **kwargs): + manager = query_utility(ISecurityManager) + if not manager.enable_social_login: + return None + return Viewlet.__new__(cls) + + @property + def use_popup(self): + 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'] + } +} + + +@view_config(route_name='login') +def login(request): + """Login view for Authautomatic authentication""" + # check security manager utility + manager = query_utility(ISecurityManager) + if (manager is None) or not manager.enable_social_login: + raise HTTPNotFound() + # store referer + session = request.session + if LOGIN_REFERER_KEY not in session: + referer = request.referer + session[LOGIN_REFERER_KEY] = referer + # init authomatic + provider_name = request.matchdict.get('provider_name') + authomatic = Authomatic(config=CONFIG, secret=manager.authomatic_secret, logging_level=WARNING) + # perform login + response = Response() + result = authomatic.login(WebObAdapter(request, response), provider_name) + if result: + if result.error: + pass + 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, + user=result.user)) + headers = remember(request, principal_id) + response.headerlist.extend(headers) + if manager.social_login_use_popup: + response.text = result.popup_html() + response.status_code = 302 + if LOGIN_REFERER_KEY in session: + response.location = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + else: + response.location = '/' + return response diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/templates/social-login.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/templates/social-login.pt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,10 @@ + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/templates/user-registration-end.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/templates/user-registration-end.pt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,22 @@ + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/templates/user-registration.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/templates/user-registration.pt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,8 @@ + \ No newline at end of file diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/userfolder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/userfolder.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,222 @@ +# +# 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_form.interfaces.form import IWidgetsSuffixViewletsManager +from pyams_security.interfaces import ILoginView, ISecurityManager, IUserRegistrationInfo, \ + IUserRegistrationConfirmationInfo +from pyams_skin.interfaces import IModalFullPage +from pyams_skin.layer import IPyAMSLayer +from z3c.form.interfaces import HIDDEN_MODE, IDataExtractedEvent + +# import packages +from pyams_form.form import DialogAddForm, AJAXAddForm, AddForm +from pyams_form.schema import CloseButton +from pyams_pagelet.pagelet import pagelet_config +from pyams_security.plugin.userfolder import User +from pyams_template.template import template_config +from pyams_utils.registry import query_utility, get_utility +from pyams_viewlet.viewlet import viewlet_config, Viewlet +from pyramid.events import subscriber +from pyramid.httpexceptions import HTTPNotFound +from pyramid.view import view_config +from z3c.form import field, button +from zope.interface import implementer, Interface, Invalid + +from pyams_security import _ + + +# +# User registration form +# + +@viewlet_config(name='user-registration-suffix', layer=IPyAMSLayer, + manager=IWidgetsSuffixViewletsManager, view=ILoginView, weight=25) +@template_config(template='templates/user-registration.pt') +class UserRegistrationViewletsSuffix(Viewlet): + """User registration viewlet suffix""" + + button_label = _("Register new account") + + def __new__(cls, *args, **kwargs): + manager = query_utility(ISecurityManager) + if not manager.open_registration: + return None + return Viewlet.__new__(cls) + + +class IUserRegistrationFormButtons(Interface): + """User registration form buttons""" + + cancel = CloseButton(name='close', title=_("Cancel")) + register = button.Button(name='register', title=_("Register")) + + +@pagelet_config(name='user-registration.html', layer=IPyAMSLayer) +class UserRegistrationForm(DialogAddForm): + """User registration form""" + + title = _("User registration") + legend = _("Please enter registration info") + icon_css_class = 'fa fa-fw fa-user' + + fields = field.Fields(IUserRegistrationInfo) + buttons = button.Buttons(IUserRegistrationFormButtons) + ajax_handler = 'user-registration.json' + edit_permission = None + + def updateActions(self): + super(UserRegistrationForm, self).updateActions() + if 'register' in self.actions: + self.actions['register'].addClass('btn-primary') + + def updateWidgets(self, prefix=None): + super(UserRegistrationForm, self).updateWidgets() + self.widgets['password'].input_css_class = 'col-md-4' + self.widgets['confirmed_password'].input_css_class = 'col-md-4' + + def create(self, data): + return User() + + def update_content(self, user, data): + user.login = data.get('login') + user.email = data.get('email') + user.firstname = data.get('firstname') + user.lastname = data.get('lastname') + user.company_name = data.get('company_name') + user.password = data.get('password') + user.generate_secret() + + def add(self, user): + manager = get_utility(ISecurityManager) + folder = manager.get(manager.users_folder) + folder[user.login] = user + + +@subscriber(IDataExtractedEvent, form_selector=UserRegistrationForm) +def handle_registration_data_extraction(event): + """Handle registration data extraction""" + data = event.data + if not data.get('login'): + data['login'] = data['email'] + manager = get_utility(ISecurityManager) + folder = manager.get(manager.users_folder) + if folder is None: + event.form.widgets.errors += (Invalid(_("Can't create user profile. Please contact system administrator.")),) + elif not folder.check_login(data.get('login')): + event.form.widgets.errors += (Invalid(_("Specified login can't be used!")),) + + +@view_config(name='user-registration.json', request_type=IPyAMSLayer, renderer='json', xhr=True) +class UserRegistrationAJAXForm(AJAXAddForm, UserRegistrationForm): + """User registration form, AJAX view""" + + def get_ajax_output(self, changes): + translate = self.request.localizer.translate + return {'status': 'success', + 'messagebox': {'status': 'success', + 'title': translate(_("Your registration is recorded!")), + 'icon': 'fa fa-fw fa-2x fa-user', + 'content': translate(_("Your registration is recorded. You should receive a " + "confirmation email soon which will allow you to " + "confirm your inscription.")), + 'timeout': None}} + + +# +# Registration confirmation form +# + +class IUserConfirmationFormButtons(Interface): + """User confirmation form buttons""" + + confirm = button.Button(name='confirm', title=_("Finalize registration")) + + +@pagelet_config(name='user-confirmation.html', layer=IPyAMSLayer) +@implementer(IModalFullPage) +class UserConfirmationForm(AddForm): + """User registration confirmation form""" + + title = _("User registration confirmation") + legend = _("Please confirm your registration info") + icon_css_class = 'fa fa-fw fa-user' + + fields = field.Fields(IUserRegistrationConfirmationInfo) + buttons = button.Buttons(IUserConfirmationFormButtons) + ajax_handler = 'user-confirmation.json' + edit_permission = None + + def updateWidgets(self, prefix=None): + super(UserConfirmationForm, self).updateWidgets() + self.widgets['activation_hash'].mode = HIDDEN_MODE + self.widgets['activation_hash'].value = self.request.form.get('hash') or self.widgets['activation_hash'].value + + def updateActions(self): + super(UserConfirmationForm, self).updateActions() + if 'confirm' in self.actions: + self.actions['confirm'].addClass('btn-primary') + + def createAndAdd(self, data): + user = data.get('user') + if user is None: + raise HTTPNotFound() + self._finishedAdd = True + + def nextURL(self): + return '/' + + +@subscriber(IDataExtractedEvent, form_selector=UserConfirmationForm) +def handle_confirmation_data_extraction(event): + """Handle confirmation data extraction""" + data = event.data + if 'user' in data: + del data['user'] + manager = get_utility(ISecurityManager) + folder = manager.get(manager.users_folder) + if folder is None: + event.form.widgets.errors += (Invalid(_("Can't check user profile. Please contact system administrator.")),) + else: + login = data.get('login') + user = folder.get(login) + if user is None: + event.form.widgets.errors += (Invalid(_("Can't retrieve user profile!")),) + else: + try: + user.check_activation(data.get('activation_hash'), login, data.get('password')) + except Invalid as error: + event.form.widgets.errors += (error,) + else: + data['user'] = user + + +@view_config(name='user-confirmation.json', request_type=IPyAMSLayer, renderer='json', xhr=True) +class UserConfirmationAJAXForm(AJAXAddForm, UserConfirmationForm): + """User registration confirmation form, AJAX view""" + + def get_ajax_output(self, changes): + return {'status': 'redirect', + 'location': 'user-registration-end.html'} + + +@pagelet_config(name='user-registration-end.html', layer=IPyAMSLayer) +@template_config(template='templates/user-registration-end.pt', layer=IPyAMSLayer) +@implementer(IModalFullPage) +class UserRegistrationEnd(object): + """User registration end""" + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/vocabulary.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/vocabulary.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,32 @@ +# +# 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.password.interfaces import IPasswordManager + +# import packages +from zope.componentvocabulary.vocabulary import UtilityVocabulary +from zope.schema.vocabulary import getVocabularyRegistry + + +class PasswordManagerVocabulary(UtilityVocabulary): + """Password managers vocabulary""" + + interface = IPasswordManager + nameOnly = True + +getVocabularyRegistry().register('PyAMS password managers', PasswordManagerVocabulary) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,20 @@ +# +# 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 + +# import packages diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/configure.zcml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/configure.zcml Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,7 @@ + + + + + + diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/interfaces.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/interfaces.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,28 @@ +# +# 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_skin.interfaces.viewlet import IMenuItem, IToolbarAction + +# import packages + + +class ISecurityManagerMenu(IMenuItem): + """Security manager menu interface""" + + +class ISecurityManagerToolbarAddingMenu(IToolbarAction): + """Security manager toolbar addings action viewlet manager""" \ No newline at end of file diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/plugin/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/plugin/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,19 @@ +# +# 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 + +# import packages diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/plugin/admin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/plugin/admin.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,120 @@ +# +# 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 IAdminAuthenticationPlugin, ISecurityManager +from pyams_security.zmi.interfaces import ISecurityManagerToolbarAddingMenu +from pyams_skin.interfaces import IContentHelp +from pyams_skin.layer import IPyAMSLayer +from z3c.form.interfaces import DISPLAY_MODE +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.admin import AdminAuthenticationPlugin +from pyams_security.zmi.utility import SecurityManagerPluginsTable +from pyams_skin.help import ContentHelp +from pyams_skin.viewlet.toolbar import ToolbarMenuItem +from pyams_utils.adapter import adapter_config +from pyams_utils.registry import query_utility +from pyams_viewlet.viewlet import viewlet_config +from pyams_zmi.form import AdminDialogEditForm, AdminDialogAddForm +from pyams_zmi.layer import IAdminLayer +from pyramid.url import resource_url +from pyramid.view import view_config +from z3c.form import field + +from pyams_security import _ + + +@viewlet_config(name='add-admin-authentication.menu', context=ISite, layer=IAdminLayer, + view=SecurityManagerPluginsTable, manager=ISecurityManagerToolbarAddingMenu, + permission='system.manage', weight=1) +class AdminAuthenticationAddMenu(ToolbarMenuItem): + """Admin authentication add menu""" + + label = _("Add admin authentication...") + label_css_class = 'fa fa-fw fa-support' + url = 'add_admin_authentication.html' + modal_target = True + + +@pagelet_config(name='add_admin_authentication.html', context=ISite, layer=IPyAMSLayer, + permission='system.manage') +class AdminAuthenticationAddForm(AdminDialogAddForm): + """Admin authentication plug-in add form""" + + title = _("System security manager") + legend = _("Add administration authentication plug-in") + icon_css_class = 'fa fa-fw fa-support' + + fields = field.Fields(IAdminAuthenticationPlugin).omit('__name__', '__parent__') + ajax_handler = 'add_admin_authentication.json' + edit_permission = 'system.manage' + + def create(self, data): + return AdminAuthenticationPlugin() + + def add(self, plugin): + context = query_utility(ISecurityManager) + context[plugin.prefix] = plugin + + def nextURL(self): + return resource_url(self.context, self.request, 'security-manager.html') + + +@view_config(name='add_admin_authentication.json', context=ISite, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class AdminAuthenticationAJAXAddForm(AJAXAddForm, AdminAuthenticationAddForm): + """Admin authentication plug-in add form, AJAX handler""" + + +@pagelet_config(name='properties.html', context=IAdminAuthenticationPlugin, layer=IPyAMSLayer, + permission='system.view') +class AdminAuthenticationEditForm(AdminDialogEditForm): + """Admin authentication plug-in edit form""" + + @property + def title(self): + return self.context.title + + legend = _("Edit administration authentication plug-in") + icon_css_class = 'fa fa-fw fa-support' + + fields = field.Fields(IAdminAuthenticationPlugin).omit('__name__', '__parent__') + ajax_handler = 'properties.json' + edit_permission = 'system.manage' + + def updateWidgets(self, prefix=None): + super(AdminAuthenticationEditForm, self).updateWidgets() + self.widgets['prefix'].mode = DISPLAY_MODE + + +@view_config(name='properties.json', context=IAdminAuthenticationPlugin, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class AdminAuthenticationAJAXEditForm(AJAXEditForm, AdminAuthenticationEditForm): + """Admin authentication plug-in edit form, AJAX handler""" + + +@adapter_config(context=(IAdminAuthenticationPlugin, IAdminLayer, AdminAuthenticationEditForm), provides=IContentHelp) +class AdminAuthenticationHelpAdapter(ContentHelp): + """Admin authentication edit form help adapter""" + + header = _("WARNING") + status = 'danger' + message = _("""Before disabling plug-in, please verify that you have other administration access!""") + message_format = 'rest' diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/plugin/directory.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/plugin/directory.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,20 @@ +# +# 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 + +# import packages diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/plugin/userfolder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/plugin/userfolder.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,367 @@ +# +# 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 IUsersFolderPlugin, ISecurityManager, ILocalUser, IUserRegistrationInfo +from pyams_security.zmi.interfaces import ISecurityManagerToolbarAddingMenu, ISecurityManagerMenu +from pyams_skin.interfaces.viewlet import IMenuItem, 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 +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.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.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 _ + + +@viewlet_config(name='add-users-folder.menu', context=ISite, layer=IAdminLayer, + view=SecurityManagerPluginsTable, manager=ISecurityManagerToolbarAddingMenu, + permission='system.manage', weight=10) +class UsersFolderAddMenu(ToolbarMenuItem): + """Local users folder add menu""" + + label = _("Add local users folder...") + label_css_class = 'fa fa-fw fa-user' + url = 'add-users-folder.html' + modal_target = True + + +@pagelet_config(name='add-users-folder.html', context=ISite, layer=IPyAMSLayer, + permission='system.manage') +class UsersFolderAddForm(AdminDialogAddForm): + """Users folder plug-in add form""" + + title = _("System security manager") + legend = _("Add local users folder plug-in") + icon_css_class = 'fa fa-fw fa-user' + + fields = field.Fields(IUsersFolderPlugin).omit('__name__', '__parent__') + ajax_handler = 'add-users-folder.json' + edit_permission = None + + def create(self, data): + return UsersFolder() + + 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-users-folder.json', context=ISite, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class UsersFolderAJAXAddForm(AJAXAddForm, UsersFolderAddForm): + """users folder plug-in add form, AJAX handler""" + + +@pagelet_config(name='properties.html', context=IUsersFolderPlugin, layer=IPyAMSLayer, + permission='system.view') +class UsersFolderEditForm(AdminDialogEditForm): + """Users folder plug-in edit form""" + + @property + def title(self): + return self.context.title + + legend = _("Edit local users folder plug-in properties") + icon_css_class = 'fa fa-fw fa-user' + + fields = field.Fields(IUsersFolderPlugin).omit('__name__', '__parent__') + ajax_handler = 'properties.json' + edit_permission = 'system.manage' + + def updateWidgets(self, prefix=None): + super(UsersFolderEditForm, self).updateWidgets() + self.widgets['prefix'].mode = DISPLAY_MODE + + +@view_config(name='properties.json', context=IUsersFolderPlugin, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class UsersFolderAJAXEditForm(AJAXEditForm, UsersFolderEditForm): + """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 + + +@pagelet_config(name='search.html', context=IUsersFolderPlugin, layer=IPyAMSLayer, permission='system.view') +class UsersFolderSearchView(AdminView, SearchView): + """Users folder search view""" + + def __init__(self, context, request): + super(UsersFolderSearchView, self).__init__(context, request) + + +@adapter_config(context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchView), provides=IPageHeader) +class UsersFolderSearchViewHeaderAdapter(ContextRequestViewAdapter): + """Users folder search view header adapter""" + + icon_class = 'fa fa-fw fa-user' + + @property + def title(self): + return self.context.title + + subtitle = _("Search users") + + +@view_config(name='search-results.html', context=IUsersFolderPlugin, request_type=IPyAMSLayer, + permission='system.view') +class UsersFolderSearchResultsView(AdminView, SearchResultsView): + """Users folder search results view table""" + + id = '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(UsersFolderSearchResultsView, self).__init__(context, request) + apply_skin(self.request, 'PyAMS admin skin') + + +@adapter_config(name='login', context=(IUsersFolderPlugin, IAdminLayer, UsersFolderSearchResultsView), + provides=IColumn) +class LoginColumn(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): + """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): + """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): + """Users registration date column""" + + _header = _("Registration date") + weight = 30 + + def getValue(self, obj): + dc = IZopeDublinCore(obj, None) + if dc is not None: + return format_datetime(dc.created) + 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): + """Users activation date column""" + + _header = _("Activation date") + weight = 40 + + def getValue(self, obj): + if obj.activation_date: + return format_datetime(obj.activation_date) + else: + return '--' + + @property + def header(self): + return self.request.localizer.translate(self._header) + + +@viewlet_config(name='users-folder.toolbar.adding', context=IUsersFolderPlugin, + view=UsersFolderSearchView.search_form_factory, layer=IAdminLayer, + manager=IToolbarViewletManager, permission='system.manage') +class UsersFolderAddAction(ToolbarAction): + """Users folder adding action""" + + label = _("Add user") + url = 'add-user.html' + modal_target = True + + +@pagelet_config(name='add-user.html', context=IUsersFolderPlugin, layer=IPyAMSLayer, permission='system.manage') +class LocalUserAddForm(AdminDialogAddForm): + """Local user add form""" + + @property + def title(self): + return self.context.title + + legend = _("Add new local user") + icon_css_class = 'fa fa-fw fa-user' + label_css_class = 'control-label col-md-4' + input_css_class = 'col-md-8' + + fields = field.Fields(IUserRegistrationInfo).select('login', 'email', 'firstname', 'lastname', 'company_name') + \ + field.Fields(ILocalUser).select('password_manager') + \ + field.Fields(IUserRegistrationInfo).select('password', 'confirmed_password') + \ + field.Fields(ILocalUser).select('wait_confirmation') + + ajax_handler = 'add-user.json' + edit_permission = 'system.manage' + + def updateWidgets(self, prefix=None): + super(LocalUserAddForm, self).updateWidgets() + self.widgets['password'].input_css_class = 'col-md-4' + self.widgets['confirmed_password'].input_css_class = 'col-md-4' + + def create(self, data): + return User() + + def update_content(self, user, data): + user.login = data.get('login') + user.email = data.get('email') + user.firstname = data.get('firstname') + user.lastname = data.get('lastname') + user.company_name = data.get('company_name') + user.password_manager = data.get('password_manager') + user.password = data.get('password') + user.self_registered = False + if data.get('wait_confirmation'): + user.generate_secret() + else: + user.activation_date = datetime.utcnow() + user.activated = True + user.wait_confirmation = data.get('wait_confirmation') + + def add(self, user): + self.context[user.login] = user + + +@subscriber(IDataExtractedEvent, form_selector=LocalUserAddForm) +def handle_new_user_data_extraction(event): + """Handle new user form data extraction""" + data = event.data + if not data.get('login'): + data['login'] = data['email'] + folder = event.form.context + if not folder.check_login(data.get('login')): + event.form.widgets.errors += (Invalid(_("Specified login can't be used!")),) + + +@view_config(name='add-user.json', context=IUsersFolderPlugin, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class LocalUserAJAXAddForm(AJAXAddForm, LocalUserAddForm): + """Local user add form, AJAX view""" + + def get_ajax_output(self, changes): + translate = self.request.localizer.translate + return {'status': 'success', + 'message': translate(_("User was created successfully"))} + + +@pagelet_config(name='properties.html', context=ILocalUser, layer=IPyAMSLayer, permission='system.view') +class LocalUserEditForm(AdminDialogEditForm): + """Local user edit form""" + + @property + def title(self): + return self.context.title + + legend = _("Edit user properties") + icon_css_class = 'fa fa-fw fa-user' + label_css_class = 'control-label col-md-4' + input_css_class = 'col-md-8' + + fields = field.Fields(ILocalUser).select('login', 'email', 'firstname', 'lastname', 'company_name', + 'self_registered', 'activation_hash', 'activation_date') + + ajax_handler = 'properties.json' + edit_permission = 'system.manage' + + def updateWidgets(self, prefix=None): + super(LocalUserEditForm, self).updateWidgets() + self.widgets['self_registered'].mode = DISPLAY_MODE + self.widgets['activation_hash'].mode = DISPLAY_MODE + self.widgets['activation_date'].mode = DISPLAY_MODE + + +@view_config(name='properties.json', context=ILocalUser, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class LocalUserAJAXEditForm(AJAXEditForm, LocalUserEditForm): + """Local user edit form, AJAX view""" diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/utility.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/utility.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,180 @@ +# +# 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, IDirectorySearchPlugin +from pyams_security.zmi.interfaces import ISecurityManagerMenu, ISecurityManagerToolbarAddingMenu +from pyams_skin.interfaces import IInnerPage, IPageHeader +from pyams_skin.interfaces.container import ITableElementEditor +from pyams_skin.interfaces.viewlet import IToolbarViewletManager +from pyams_skin.layer import IPyAMSLayer +from pyams_zmi.interfaces.menu import IControlPanelMenu +from pyams_zmi.layer import IAdminLayer +from z3c.table.interfaces import IValues, IColumn +from zope.component.interfaces import ISite + +# import packages +from pyams_form.form import AJAXEditForm +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.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_viewlet.manager import viewletmanager_config +from pyams_viewlet.viewlet import viewlet_config +from pyams_zmi.form import AdminDialogEditForm +from pyams_zmi.view import AdminView +from pyramid.url import resource_url +from pyramid.view import view_config +from z3c.form import field +from zope.interface import implementer, Interface + +from pyams_security import _ + + +@adapter_config(context=(ISecurityManager, IAdminLayer, Interface), provides=ITableElementEditor) +class SecurityManagerTableElementEditor(DefaultElementEditorAdapter): + """Security manager table element editor""" + + view_name = 'security-manager.html' + modal_target = False + + @property + def url(self): + site = get_parent(self.context, ISite) + return resource_url(site, self.request, 'admin.html#{0}'.format(self.view_name)) + + +@viewlet_config(name='security-manager.menu', context=ISite, layer=IAdminLayer, manager=IControlPanelMenu, + permission='system.view', weight=5) +@viewletmanager_config(name='security-manager.menu', context=ISite, layer=IAdminLayer) +@implementer(ISecurityManagerMenu) +class SecurityManagerMenuItem(MenuItem): + """Security manager menu""" + + label = _("Security") + icon_class = 'fa fa-fw fa-lock' + url = '#security-manager.html' + + +class SecurityManagerPluginsTable(BaseTable): + """Security manager plug-ins table""" + + id = 'security_manager_table' + title = _("Authentication and users directory plug-ins") + cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight datatable'} + + +@adapter_config(name='search', context=(Interface, IAdminLayer, SecurityManagerPluginsTable), provides=IColumn) +class SecurityManagerPluginsSearchColumn(ActionColumn): + """Security manager plugins search column""" + + url = "search.html" + weight = 100 + + def renderCell(self, item): + if not IDirectorySearchPlugin.providedBy(item): + return '' + return super(SecurityManagerPluginsSearchColumn, self).renderCell(item) + + +@adapter_config(context=(ISite, IAdminLayer, SecurityManagerPluginsTable), provides=IValues) +class SecurityManagerValuesAdapter(ContextRequestViewAdapter): + """Security manager values adapter""" + + @property + def values(self): + manager = query_utility(ISecurityManager) + if manager is not None: + return list(manager.values()) + return () + + +@pagelet_config(name='security-manager.html', context=ISite, layer=IPyAMSLayer, permission='system.view') +@implementer(IInnerPage) +class SecurityManagerView(AdminView, ContainerView): + """Security manager view""" + + table_class = SecurityManagerPluginsTable + + def __init__(self, context, request): + super(SecurityManagerView, self).__init__(context, request) + + +@adapter_config(context=(ISite, IAdminLayer, SecurityManagerView), provides=IPageHeader) +class SecurityManagerHeaderAdapter(ContextRequestViewAdapter): + """Security manager header adapter""" + + icon_class = 'fa fa-fw fa-lock' + title = _("Security manager") + subtitle = _("Security manager plug-ins") + + +@viewlet_config(name='security-manager.toolbar.adding', context=ISite, view=SecurityManagerPluginsTable, + layer=IAdminLayer, manager=IToolbarViewletManager, permission='system.manage') +@viewletmanager_config(name='security-manager.toolbar.adding', context=ISite, view=SecurityManagerPluginsTable, + layer=IAdminLayer) +@implementer(ISecurityManagerToolbarAddingMenu) +class SecurityManagerToolbarAddingsAction(ToolbarMenu): + """Security manager toolbar adding action""" + + label = _("Add...") + + +@viewlet_config(name='security-manager.properties.menu', context=ISite, layer=IAdminLayer, + manager=ISecurityManagerMenu, permission='system.view') +class SecurityManagerPropertiesMenuItem(MenuItem): + """Security manager properties menu""" + + label = _("Properties...") + url = 'properties.html' + modal_target = True + + def get_url(self): + manager = query_utility(ISecurityManager) + return resource_url(manager, self.request, self.url) + + +@pagelet_config(name='properties.html', context=ISecurityManager, layer=IPyAMSLayer, permission='system.view') +class SecurityManagerEditForm(AdminDialogEditForm): + """Security manager edit form""" + + 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' + + fields = field.Fields(ISecurityManager) + fields['credentials_plugins_names'].widgetFactory = OrderedPluginsFieldWidget + fields['authentication_plugins_names'].widgetFactory = OrderedPluginsFieldWidget + fields['directory_plugins_names'].widgetFactory = OrderedPluginsFieldWidget + + ajax_handler = 'properties.json' + + def getContent(self): + return query_utility(ISecurityManager) + + +@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""" diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/widget/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/widget/__init__.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,56 @@ +# +# 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. +# +from pyams_form.interfaces.form import IFormLayer +from pyams_form.widget import widgettemplate_config + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from z3c.form.interfaces import IDataConverter +from zope.schema.interfaces import ITuple + +# import packages +from pyams_utils.adapter import adapter_config +from z3c.form.browser.widget import HTMLFormElement +from z3c.form.converter import BaseDataConverter +from z3c.form.widget import FieldWidget, Widget + + +@widgettemplate_config(mode='input', template='templates/ordered-list-input.pt', layer=IFormLayer) +@widgettemplate_config(mode='display', template='templates/ordered-list-display.pt', layer=IFormLayer) +class OrderedPluginsWidget(HTMLFormElement, Widget): + """Ordered plug-ins list widget""" + + def str_value(self): + return ';'.join(self.value) + + def items(self): + for name in self.value: + yield self.context.get_plugin(name) + + +def OrderedPluginsFieldWidget(field, request): + return FieldWidget(field, OrderedPluginsWidget(request)) + + +@adapter_config(context=(ITuple, OrderedPluginsWidget), provides=IDataConverter) +class OrderdedPluginsDataConverter(BaseDataConverter): + """Ordered plugins data converter""" + + def toWidgetValue(self, value): + return value + + def toFieldValue(self, value): + return tuple(value.split(';')) diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/widget/templates/ordered-list-display.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/widget/templates/ordered-list-display.pt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,6 @@ + + + + +
diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/zmi/widget/templates/ordered-list-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/zmi/widget/templates/ordered-list-input.pt Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,16 @@ + + + + + +