--- /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$
--- /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
--- /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.
--- /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 *.*~
--- /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)
--- /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
--- /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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+"""
+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'
+ ]
+ })
--- /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
--- /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
--- /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 @@
+
--- /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
+
--- /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 @@
+
--- /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 @@
+
--- /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]
--- /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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from 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)
--- /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 @@
+<configure
+ xmlns="http://pylonshq.com/pyramid"
+ xmlns:i18n="http://namespaces.zope.org/i18n"
+ xmlns:zcml="http://namespaces.zope.org/zcml">
+
+ <include package="pyramid_zcml" />
+ <include package="zope.component" file="meta.zcml" />
+ <include package="zope.i18n" file="meta.zcml" />
+
+ <i18n:registerTranslations directory="locales" />
+
+
+ <!-- Zope password registrations -->
+ <configure zcml:condition="installed zope.password">
+
+ <utility
+ name="Plain Text"
+ provides="zope.password.interfaces.IPasswordManager"
+ factory="zope.password.password.PlainTextPasswordManager" />
+
+ <utility
+ name="MD5"
+ provides="zope.password.interfaces.IPasswordManager"
+ factory="zope.password.password.MD5PasswordManager" />
+
+ <utility
+ name="SHA1"
+ provides="zope.password.interfaces.IPasswordManager"
+ factory="zope.password.password.SHA1PasswordManager" />
+
+ <utility
+ name="SSHA"
+ provides="zope.password.interfaces.IPasswordManager"
+ factory="zope.password.password.SSHAPasswordManager" />
+
+ </configure>
+
+
+ <!-- ZMI package -->
+ <configure zcml:condition="installed pyams_zmi">
+ <include package=".zmi" />
+ </configure>
+
+</configure>
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import 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)
--- /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
+======================
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# 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')
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import 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'])
Binary file src/pyams_security/locales/fr/LC_MESSAGES/pyams_security.mo has changed
--- /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 <tflorac@ulthar.net>, 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 <tflorac@ulthar.net>\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 !"
--- /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 <tflorac@ulthar.net>, 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 <tflorac@ulthar.net>\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 !"
--- /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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 ""
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import 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)
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import 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)
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import 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 '<MissingPrincipal: {0}>'.format(self.id)
+
+ def __eq__(self, other):
+ return isinstance(other, PrincipalInfo) and (self.id == other.id)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/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
+*/
--- /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<I;G++){K=H[G];E=K.split("="),F=E[0],J=E[1];J=decodeURIComponent(J);if(L.hasOwnProperty(F)){if(Array.isArray(L[F])){L[F].push(J)}else{L[F]=[L[F],J]}}else{L[F]=J}}return L};v=function(G){var E,D,F;m("parseUrl",G);D=G.indexOf("?");if(D>=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<G;J++){L=I[J];K=K[L]}return K})};window.authomatic=new (k=(function(){function D(){}D.prototype.setup=function(E){l.extend(c,E);return m("Setting up authomatic to:",c)};D.prototype.popupInit=function(){l(c.popupLinkSelector).click(function(E){E.preventDefault();return A(l(this).attr("href"))});return l(c.popupFormSelector).submit(function(G){var E,F;G.preventDefault();E=l(this);F=E.attr("action")+"?"+E.serialize();if(c.popupFormValidator(E)){return A(F)}else{return typeof c.onPopupInvalid==="function"?c.onPopupInvalid(E):void 0}})};D.prototype.loginComplete=function(E,G){var F;F=l.extend(true,{},E);m("Login procedure complete",F);G();return c.onLoginComplete(F)};D.prototype.access=function(I,G,F){var K,H,J,E;if(F==null){F={}}H={onBackendStart:null,onBackendComplete:null,onAccessSuccess:null,onAccessComplete:null};E={};l.extend(E,c,H,F);G=y(G,E.substitute);m("access options",E,c);if(E.forceBackend){K=z}else{K=g(I)}J=new K(F.backend,I,G,E);m("Instantiating provider:",J);return J.access()};return D})());z=(function(){D.prototype._x_jsonpCallbackParamName="callback";function D(H,G,F,E){var I;this.backend=H;this.credentials=G;this.options=E;this.access=j(this.access,this);this.contactProvider=j(this.contactProvider,this);this.contactBackend=j(this.contactBackend,this);this.backendRequestType="auto";this.jsonpRequest=false;this.jsonpCallbackName=""+c.jsonpCallbackPrefix+o;this.deserializedCredentials=s(this.credentials);this.providerID=this.deserializedCredentials.id;this.providerType=this.deserializedCredentials.type;this.credentialsRest=this.deserializedCredentials.rest;I=v(F);this.url=I.url;this.params={};l.extend(this.params,I.params,this.options.params)}D.prototype.contactBackend=function(G){var F,E;if(this.jsonpRequest&&this.options.method===!"GET"){this.backendRequestType="fetch"}F={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 c.onBackendStart==="function"){c.onBackendStart(F)}if(typeof(E=this.options).onBackendStart==="function"){E.onBackendStart(F)}m("Contacting backend at "+this.options.backend+".",F);return l.get(this.options.backend,F,G)};D.prototype.contactProvider=function(G){var K,H,J,E,M,I,F,L=this;F=G.url,E=G.method,I=G.params,H=G.headers,K=G.body;M={type:E,data:I,headers:H,complete:[(function(N,O){return m("Request complete.",O,N)}),c.onAccessComplete,this.options.onAccessComplete],success:[(function(N){return m("Request successful.",N)}),c.onAccessSuccess,this.options.onAccessSuccess],error:function(N,P,O){if(N.state()==="rejected"){if(L.options.method==="GET"){m("Cross domain request failed! trying JSONP request.");L.jsonpRequest=true}else{L.backendRequestType="fetch"}return L.access()}}};if(this.jsonpRequest){J={jsonpCallback:this.jsonpCallbackName,jsonp:this._x_jsonpCallbackParamName,cache:true,dataType:"jsonp",error:function(N,P,O){return m("JSONP failed! State:",N.state())}};l.extend(M,J);m("Contacting provider with JSONP request.",F,M)}else{m("Contacting provider with cross domain request",F,M)}return l.ajax(F,M)};D.prototype.access=function(){var F,E=this;F=function(L,M,K){var G,I,J,H;if(typeof c.onBackendComplete==="function"){c.onBackendComplete(L,M,K)}if(typeof(I=E.options).onBackendComplete==="function"){I.onBackendComplete(L,M,K)}G=K!=null?K.getResponseHeader("Authomatic-Response-To"):void 0;if(G==="fetch"){m("Fetch data returned from backend.",K.getResponseHeader("content-type"),L);if(typeof c.onAccessSuccess==="function"){c.onAccessSuccess(L)}if(typeof(J=E.options).onAccessSuccess==="function"){J.onAccessSuccess(L)}if(typeof c.onAccessComplete==="function"){c.onAccessComplete(K,M)}return typeof(H=E.options).onAccessComplete==="function"?H.onAccessComplete(K,M):void 0}else{if(G==="elements"){m("Request elements data returned from backend.",L);return E.contactProvider(L)}}};if(this.jsonpRequest){o+=1}return this.contactBackend(F)};return D})();t=(function(E){B(D,E);function D(){this.contactProvider=j(this.contactProvider,this);this.access=j(this.access,this);n=D.__super__.constructor.apply(this,arguments);return n}D.prototype.access=function(){this.jsonpRequest=true;this.params[this._x_jsonpCallbackParamName]=this.jsonpCallbackName;return D.__super__.access.call(this)};D.prototype.contactProvider=function(F){delete F.params.callback;return D.__super__.contactProvider.call(this,F)};return D})(z);C=(function(D){B(E,D);function E(){i=E.__super__.constructor.apply(this,arguments);return i}E.prototype._x_jsonpCallbackParamName="jsoncallback";return E})(t);q=(function(E){B(D,E);D.prototype._x_accessToken="access_token";D.prototype._x_bearer="Bearer";function D(){var F,G;F=1<=arguments.length?r.call(arguments,0):[];this.access=j(this.access,this);this.handleTokenType=j(this.handleTokenType,this);D.__super__.constructor.apply(this,F);G=this.credentialsRest,this.accessToken=G[0],this.refreshToken=G[1],this.expirationTime=G[2],this.tokenType=G[3];this.handleTokenType()}D.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}};D.prototype.access=function(){var F;if(this.backendRequestType==="fetch"){return D.__super__.access.call(this)}else{F={url:this.url,method:this.options.method,params:this.params,headers:this.options.headers,body:this.options.body};return this.contactProvider(F)}};return D})(z);w=(function(E){B(D,E);function D(){h=D.__super__.constructor.apply(this,arguments);return h}D.prototype._x_accessToken="oauth_token";return D})(q);u=(function(D){B(E,D);function E(){f=E.__super__.constructor.apply(this,arguments);return f}E.prototype._x_bearer="OAuth";return E})(q);b=(function(E){B(D,E);function D(){e=D.__super__.constructor.apply(this,arguments);return e}D.prototype._x_accessToken="oauth2_access_token";return D})(q);d=(function(D){B(E,D);function E(){this.handleTokenType=j(this.handleTokenType,this);a=E.__super__.constructor.apply(this,arguments);return a}E.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 E})(q)}).call(this);
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/resources/js/security.js Thu Feb 19 10:53:29 2015 +0100
@@ -0,0 +1,44 @@
+(function($) {
+
+ window.PyAMS_security = {
+
+ /**
+ * Init social plug-in
+ */
+ init_social: function (element) {
+
+ MyAMS.ajax.check(window.authomatic,
+ '/--static--/pyams_security/js/authomatic' + (MyAMS.devmode ? '.min.js' : '.js'),
+ function (first_load) {
+ authomatic.setup({
+ popupWidth: 800,
+ popupHeight: 400,
+ onLoginComplete: function(result) {
+ console.log(result);
+ if (result.error) {
+
+ } else if (result.user) {
+ alert(result.user);
+ }
+ }
+ });
+ authomatic.popupInit();
+ });
+ },
+
+ /**
+ * Ordered plug-ins callback
+ */
+ plugins: {
+
+ /**
+ * Change plug-ins names order
+ */
+ changeOrder: function(table, names) {
+ var input = $('input[name="' + $(this).data('ams-input-name') + '"]', $(this));
+ input.val(names.join(';'));
+ }
+ }
+ };
+
+})(jQuery);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/resources/js/security.min.js Thu Feb 19 10:53:29 2015 +0100
@@ -0,0 +1,1 @@
+(function(a){window.PyAMS_security={init_social:function(b){MyAMS.ajax.check(window.authomatic,"/--static--/pyams_security/js/authomatic"+(MyAMS.devmode?".min.js":".js"),function(c){authomatic.setup({popupWidth:800,popupHeight:400,onLoginComplete:function(d){console.log(d);if(d.error){}else{if(d.user){alert(d.user)}}}});authomatic.popupInit()})},plugins:{changeOrder:function(c,d){var b=a('input[name="'+a(this).data("ams-input-name")+'"]',a(this));b.val(d.join(";"))}}}})(jQuery);
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/role.py Thu Feb 19 10:53:29 2015 +0100
@@ -0,0 +1,52 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import IRole
+
+# 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)
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import ISecurityManager
+from pyams_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
--- /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 @@
+
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+"""
+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')
+
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+"""
+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')
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import ISecurityManager, 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import 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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from 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
--- /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 @@
+<div class="social">
+ <span tal:condition="view.use_popup"
+ data-ams-plugins="pyams_security"
+ data-ams-plugin-pyams_security-src="/--static--/pyams_security/js/security.js"
+ data-ams-plugin-pyams_security-callback="PyAMS_security.init_social"></span>
+ <span>Login with: </span>
+ <a class="authomatic" href="/login/facebook">Facebook</a>
+ <a class="authomatic" href="/login/twitter">Twitter</a>
+ <a class="authomatic" href="/login/google">Google</a>
+</div>
--- /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 @@
+<div class="modal-dialog modal-medium">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h3 class="modal-title">
+ <span class="title">Registration complete!</span>
+ </h3>
+ </div>
+ <div class="modal-body no-padding">
+ <div class="ams-form">
+ <div class="alert alert-success">
+ Your registration is complete.<br />
+ You can now return to home page."
+ </div>
+ <footer>
+ <a class="btn btn-success" href="/">
+ Return to home page
+ </a>
+ </footer>
+ </div>
+ </div>
+ </div>
+</div>
--- /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 @@
+<div>
+ <div class="pull-right">
+ <a class="btn btn-sm btn-success"
+ href="user-registration.html" data-toggle="modal"
+ tal:content="view.button_label">Register new account</a>
+ <div class="clearfix"></div>
+ </div>
+</div>
\ No newline at end of file
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_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"""
+
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from zope.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)
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /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 @@
+<configure
+ xmlns="http://pylonshq.com/pyramid">
+
+ <include package="pyramid_zcml" />
+ <include package="z3c.form" file="meta.zcml" />
+
+</configure>
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_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
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import 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'
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from datetime import datetime
+
+# import interfaces
+from pyams_security.interfaces import 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"""
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_security.interfaces import ISecurityManager, 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"""
--- /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 <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+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(';'))
--- /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 @@
+<table class="table table-bordered table-striped table-hover table-tight"
+ tal:attributes="id string:${view/field/getName}_list">
+ <tr tal:repeat="item view/items">
+ <td tal:content="item/title | item"></td>
+ </tr>
+</table>
--- /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 @@
+<table class="table table-bordered table-striped table-hover table-tight table-dnd"
+ data-ams-tablednd-drop-target="PyAMS_security.plugins.changeOrder"
+ data-ams-plugins="security"
+ data-ams-plugin-security-src="/--static--/pyams_security/js/security.js"
+ tal:attributes="id string:${view/field/getName}_list;
+ data-ams-input-name view/name;">
+ <input type="hidden"
+ tal:attributes="id view/id;
+ name view/name;
+ value view/str_value;" />
+ <tr tal:repeat="item view/items"
+ tal:attributes="id string:${view/field/getName}::${item/__name__ | default};
+ data-ams-element-name item/__name__ | default;">
+ <td tal:content="item/title | item"></td>
+ </tr>
+</table>