diff --git a/auth_from_http_remote_user/__init__.py b/auth_from_http_remote_user/__init__.py
new file mode 100644
index 000000000..6e0a37c8b
--- /dev/null
+++ b/auth_from_http_remote_user/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from . import controllers
+from . import res_config
+from . import res_users
diff --git a/auth_from_http_remote_user/__openerp__.py b/auth_from_http_remote_user/__openerp__.py
new file mode 100644
index 000000000..317cdb073
--- /dev/null
+++ b/auth_from_http_remote_user/__openerp__.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+{
+ 'name': 'Authenticate via HTTP Remote User',
+ 'version': '1.0',
+ 'category': 'Tools',
+ 'description': """
+Allow users to be automatically logged in.
+==========================================
+
+This module initialize the session by looking for the field HTTP_REMOTE_USER in
+the HEADER of the HTTP request and trying to bind the given value to a user
+This module must be loaded at startup; Add the *--load* parameter to the startup
+command: ::
+
+ --load=web,web_kanban,auth_from_http_remote_user, ...
+
+If the field is not found or no user matches the given one, it can lets the
+system redirect to the login page (default) or issue a login error page depending
+of the configuration.
+
+How to test the module with Apache [#]_
+----------------------------------------
+
+Apache can be used as a reverse proxy providing the authentication and adding the
+required field in the Http headers.
+
+Install apache: ::
+
+ $ sudo apt-get install apache2
+
+
+Define a new vhost to Apache by putting a new file in /etc/apache2/sites-available: ::
+
+ $ sudo vi /etc/apache2/sites-available/MY_VHOST.com
+
+with the following content: ::
+
+
+ ServerName MY_VHOST.com
+ ProxyRequests Off
+
+ AuthType Basic
+ AuthName "Test OpenErp auth_from_http_remote_user"
+ AuthBasicProvider file
+ AuthUserFile /etc/apache2/MY_VHOST.htpasswd
+ Require valid-user
+
+ RewriteEngine On
+ RewriteCond %{LA-U:REMOTE_USER} (.+)
+ RewriteRule . - [E=RU:%1]
+ RequestHeader set Remote-User "%{RU}e" env=RU
+
+
+ ProxyPass / http://127.0.0.1:8069/ retry=10
+ ProxyPassReverse / http://127.0.0.1:8069/
+ ProxyPreserveHost On
+
+
+.. important:: The *RequestHeader* directive is used to add the *Remote-User* field
+ in the http headers. By default an *'Http-'* prefix is added to the field name.
+ In OpenErp, header's fields name are normalized. As result of this normalization,
+ the 'Http-Remote-User' is available as 'HTTP_REMOTE_USER'. If you don't know how
+ your specified field is seen by OpenErp, run your server in debug mode once the
+ module is activated and look for an entry like: ::
+
+ DEBUG openerp1 openerp.addons.auth_from_http_remote_user.controllers.session:
+ Field 'HTTP_MY_REMOTE_USER' not found in http headers
+ {'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', ..., 'HTTP_REMOTE_USER': 'demo')
+
+Enable the required apache modules: ::
+
+ $ sudo a2enmod headers
+ $ sudo a2enmod proxy
+ $ sudo a2enmod rewrite
+ $ sudo a2enmod proxy_http
+
+Enable your new vhost: ::
+
+ $ sudo a2ensite MY_VHOST.com
+
+Create the *htpassword* file used by the configured basic authentication: ::
+
+ $ sudo htpasswd -cb /etc/apache2/MY_VHOST.htpasswd admin admin
+ $ sudo htpasswd -b /etc/apache2/MY_VHOST.htpasswd demo demo
+
+For local test, add the *MY_VHOST.com* in your /etc/vhosts file.
+
+Finally reload the configuration: ::
+
+ $ sudo service apache2 reload
+
+Open your browser and go to MY_VHOST.com. If everything is well configured, you are prompted
+for a login and password outside OpenErp and are automatically logged in the system.
+
+.. [#] Based on a ubuntu 12.04 env
+
+""",
+ 'author': 'Acsone SA/NV',
+ 'maintainer': 'ACSONE SA/NV',
+ 'website': 'http://www.acsone.eu',
+ 'depends': ['web'],
+ "license": "AGPL-3",
+ "js": ['static/src/js/auth_from_http_remote_user.js'],
+ 'data': [
+ 'res_config_view.xml',
+ 'res_config_data.xml'],
+ "demo": [],
+ "test": [],
+ "active": False,
+ "license": "AGPL-3",
+ "installable": True,
+ "auto_install": False,
+ "application": False,
+}
diff --git a/auth_from_http_remote_user/controllers/__init__.py b/auth_from_http_remote_user/controllers/__init__.py
new file mode 100644
index 000000000..7705efcef
--- /dev/null
+++ b/auth_from_http_remote_user/controllers/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from . import session
diff --git a/auth_from_http_remote_user/controllers/session.py b/auth_from_http_remote_user/controllers/session.py
new file mode 100644
index 000000000..f1dc0450a
--- /dev/null
+++ b/auth_from_http_remote_user/controllers/session.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+
+from openerp.addons.web import http
+from openerp.addons.web.controllers import main
+from openerp.modules.registry import RegistryManager
+from .. import utils
+
+import random
+import logging
+import openerp.tools.config as config
+
+_logger = logging.getLogger(__name__)
+
+
+class Session(main.Session):
+ _cp_path = "/web/session"
+
+ _REQUIRED_ATTRIBUTES = ['HTTP_REMOTE_USER']
+ _OPTIONAL_ATTRIBUTES = []
+
+ def _get_db(self, db):
+ if db is not None and len(db) > 0:
+ return db
+ db = config['db_name']
+ if db is None or len(db) == 0:
+ _logger.error("No db found for SSO. Specify one in the URL using parameter "
+ "db=? or provide a default one in the configuration")
+ raise http.AuthenticationError()
+
+ def _get_user_id_from_attributes(self, res_users, cr, attrs):
+ login = attrs.get('HTTP_REMOTE_USER', None)
+ user_ids = res_users.search(cr, SUPERUSER_ID, [('login', '=', login), ('active', '=', True)])
+ assert len(user_ids) < 2
+ if user_ids:
+ return user_ids[0]
+ return None
+
+ def _get_attributes_form_header(self, req):
+ attrs = {}
+
+ all_attrs = self._REQUIRED_ATTRIBUTES + self._OPTIONAL_ATTRIBUTES
+
+ headers = req.httprequest.headers.environ
+
+ for attr in all_attrs:
+ value = headers.get(attr, None)
+ if value is not None:
+ attrs[attr] = value
+
+ attrs_found = set(attrs.keys())
+ attrs_missing = set(all_attrs) - attrs_found
+ if len(attrs_found) > 0:
+ _logger.debug("Fields '%s' not found in http headers\n %s", attrs_missing, headers)
+
+ missings = set(self._REQUIRED_ATTRIBUTES) - attrs_found
+ if len(missings) > 0:
+ _logger.error("Required fields '%s' not found in http headers\n %s", missings, headers)
+ return attrs
+
+ def _bind_http_remote_user(self, req, db_name):
+ db_name = self._get_db(db_name)
+ try:
+ registry = RegistryManager.get(db_name)
+ with registry.cursor() as cr:
+ modules = registry.get('ir.module.module')
+ installed = modules.search_count(cr, SUPERUSER_ID, ['&',
+ ('name', '=', 'auth_from_http_remote_user'),
+ ('state', '=', 'installed')]) == 1
+ if not installed:
+ return
+ config = registry.get('auth_from_http_remote_user.config.settings')
+ # get parameters for SSO
+ default_login_page_disabled = config.is_default_login_page_disabled(cr, SUPERUSER_ID, None)
+
+ # get the user
+ res_users = registry.get('res.users')
+ attrs = self._get_attributes_form_header(req)
+ user_id = self._get_user_id_from_attributes(res_users, cr, attrs)
+
+ if user_id is None:
+ if default_login_page_disabled:
+ raise http.AuthenticationError()
+ return
+
+ # generate a specific key for authentication
+ key = randomString(utils.KEY_LENGTH, '0123456789abcdef')
+ res_users.write(cr, SUPERUSER_ID, [user_id], {'sso_key': key})
+ login = res_users.browse(cr, SUPERUSER_ID, user_id).login
+ req.session.bind(db_name, user_id, login, key)
+ except http.AuthenticationError, e:
+ raise e
+ except Exception, e:
+ _logger.error("Error binding Http Remote User session", exc_info=True)
+ raise e
+
+ @http.jsonrequest
+ def get_http_remote_user_session_info(self, req, db):
+ if not req.session._login:
+ self._bind_http_remote_user(req, db)
+ return self.session_info(req)
+
+randrange = random.SystemRandom().randrange
+
+
+def randomString(length, chrs):
+ """Produce a string of length random bytes, chosen from chrs."""
+ n = len(chrs)
+ return ''.join([chrs[randrange(n)] for _ in xrange(length)])
diff --git a/auth_from_http_remote_user/res_config.py b/auth_from_http_remote_user/res_config.py
new file mode 100644
index 000000000..5cbc9082d
--- /dev/null
+++ b/auth_from_http_remote_user/res_config.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.osv import orm, fields
+from openerp.tools.safe_eval import safe_eval
+import types
+
+
+class auth_from_http_remote_user_configuration(orm.TransientModel):
+ _name = 'auth_from_http_remote_user.config.settings'
+ _inherit = 'res.config.settings'
+
+ _columns = {
+ 'default_login_page_disabled': fields.boolean("Disable login page",
+ help="""
+Disable the default login page.
+If the HTTP_REMOTE_HEADER field is not found or no user matches the given one,
+the system will display a login error page if the login page is disabled.
+Otherwise the normal login page will be displayed.
+ """),
+ }
+
+ def is_default_login_page_disabled(self, cr, uid, fields, context=None):
+ ir_config_obj = self.pool['ir.config_parameter']
+ default_login_page_disabled = ir_config_obj.get_param(cr,
+ uid,
+ 'auth_from_http_remote_user.default_login_page_disabled')
+ if isinstance(default_login_page_disabled, types.BooleanType):
+ return default_login_page_disabled
+ return safe_eval(default_login_page_disabled)
+
+ def get_default_default_login_page_disabled(self, cr, uid, fields, context=None):
+ default_login_page_disabled = self.is_default_login_page_disabled(cr, uid, fields, context)
+ return {'default_login_page_disabled': default_login_page_disabled}
+
+ def set_default_default_login_page_disabled(self, cr, uid, ids, context=None):
+ config = self.browse(cr, uid, ids[0], context)
+ ir_config_parameter_obj = self.pool['ir.config_parameter']
+ ir_config_parameter_obj.set_param(cr,
+ uid,
+ 'auth_from_http_remote_user.default_login_page_disabled',
+ repr(config.default_login_page_disabled))
diff --git a/auth_from_http_remote_user/res_config_data.xml b/auth_from_http_remote_user/res_config_data.xml
new file mode 100644
index 000000000..ba9a2b1c1
--- /dev/null
+++ b/auth_from_http_remote_user/res_config_data.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ auth_from_http_remote_user.default_login_page_disabled
+ False
+
+
+
diff --git a/auth_from_http_remote_user/res_config_view.xml b/auth_from_http_remote_user/res_config_view.xml
new file mode 100644
index 000000000..3869cb4ac
--- /dev/null
+++ b/auth_from_http_remote_user/res_config_view.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ Auth HTTP_REMOTE_USER settings
+ auth_from_http_remote_user.config.settings
+
+
+
+
+
+
+
+ Configure Auth HTTP_REMOTE_USER
+ ir.actions.act_window
+ auth_from_http_remote_user.config.settings
+ form
+ inline
+
+
+
+
+
+
diff --git a/auth_from_http_remote_user/res_users.py b/auth_from_http_remote_user/res_users.py
new file mode 100644
index 000000000..fef91596d
--- /dev/null
+++ b/auth_from_http_remote_user/res_users.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.modules.registry import RegistryManager
+from openerp.osv import orm, fields
+from openerp import SUPERUSER_ID
+import openerp.exceptions
+from openerp.addons.auth_from_http_remote_user import utils
+
+
+class res_users(orm.Model):
+ _inherit = 'res.users'
+
+ _columns = {
+ 'sso_key': fields.char('SSO Key', size=utils.KEY_LENGTH,
+ readonly=True),
+ }
+
+ def copy(self, cr, uid, rid, defaults=None, context=None):
+ defaults = defaults or {}
+ defaults['sso_key'] = False
+ return super(res_users, self).copy(cr, uid, rid, defaults, context)
+
+ def check_credentials(self, cr, uid, password):
+ try:
+ return super(res_users, self).check_credentials(cr, uid, password)
+ except openerp.exceptions.AccessDenied:
+ res = self.search(cr, SUPERUSER_ID, [('id', '=', uid), ('sso_key', '=', password)])
+ if not res:
+ raise openerp.exceptions.AccessDenied()
+
+ def check(self, db, uid, passwd):
+ try:
+ return super(res_users, self).check(db, uid, passwd)
+ except openerp.exceptions.AccessDenied:
+ if not passwd:
+ raise
+ with RegistryManager.get(db).cursor() as cr:
+ cr.execute('''SELECT COUNT(1)
+ FROM res_users
+ WHERE id=%s
+ AND sso_key=%s
+ AND active=%s''', (int(uid), passwd, True))
+ if not cr.fetchone()[0]:
+ raise
+ self._uid_cache.setdefault(db, {})[uid] = passwd
diff --git a/auth_from_http_remote_user/static/src/js/auth_from_http_remote_user.js b/auth_from_http_remote_user/static/src/js/auth_from_http_remote_user.js
new file mode 100644
index 000000000..df34118cf
--- /dev/null
+++ b/auth_from_http_remote_user/static/src/js/auth_from_http_remote_user.js
@@ -0,0 +1,36 @@
+openerp.auth_from_http_remote_user = function(instance) {
+
+ instance.web.Session.include({
+ session_load_response : function(response) {
+ //unregister the event since it must be called only if the rpc call
+ //is made by session_reload
+ this.off('response', this.session_load_response);
+ if (response.error && response.error.data.type === "session_invalid") {
+ $("body").html("
Access Denied
");
+ }
+
+ console.log("session_load_response called");
+ },
+
+ session_reload : function() {
+ var self = this;
+ // we need to register an handler for 'response' since
+ // by default, the rpc doesn't call callback function
+ // if the response is of error type 'session_invalid'
+ this.on('response', this, this.session_load_response);
+ return this.rpc("/web/session/get_http_remote_user_session_info", {
+ db : $.deparam.querystring().db
+ }).done(function(result) {
+ // If immediately follows a login (triggered by trying to
+ // restore
+ // an invalid session or no session at all), refresh session
+ // data
+ // (should not change, but just in case...)
+ _.extend(self, result);
+ }).fail(function(result){
+ $("body").html("Server error
");
+ });
+ }
+ });
+
+};
\ No newline at end of file
diff --git a/auth_from_http_remote_user/tests/__init__.py b/auth_from_http_remote_user/tests/__init__.py
new file mode 100644
index 000000000..bf16e2154
--- /dev/null
+++ b/auth_from_http_remote_user/tests/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from . import test_res_users
+fast_suite = [
+]
+
+checks = [
+ test_res_users,
+]
diff --git a/auth_from_http_remote_user/tests/test_res_users.py b/auth_from_http_remote_user/tests/test_res_users.py
new file mode 100644
index 000000000..e2ba2f890
--- /dev/null
+++ b/auth_from_http_remote_user/tests/test_res_users.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.tests import common
+import mock
+import os
+from contextlib import contextmanager
+import unittest
+
+
+@contextmanager
+def mock_cursor(cr):
+ with mock.patch('openerp.sql_db.Connection.cursor') as mocked_cursor_call:
+ org_close = cr.close
+ org_autocommit = cr.autocommit
+ try:
+ cr.close = mock.Mock()
+ cr.autocommit = mock.Mock()
+ mocked_cursor_call.return_value = cr
+ yield
+ finally:
+ cr.close = org_close
+ cr.autocommit = org_autocommit
+
+
+class test_res_users(common.TransactionCase):
+
+ def test_login(self):
+ res_users_obj = self.registry('res.users')
+ uid = res = res_users_obj.login(common.DB, 'admin', 'admin')
+ self.assertTrue(res, "Basic login must works as expected")
+ token = "123456"
+ res = res_users_obj.login(common.DB, 'admin', token)
+ self.assertFalse(res)
+ # mimic what the new controller do when it find a value in
+ # the http header (HTTP_REMODE_USER)
+ res_users_obj.write(self.cr, self.uid, uid, {'sso_key': token})
+
+ # Here we need to mock the cursor since the login is natively done inside
+ # its own connection
+ with mock_cursor(self.cr):
+ # We can verifies that the given (uid, token) is authorized for the database
+ res_users_obj.check(common.DB, uid, token)
+
+ # we are able to login with the new token
+ res = res_users_obj.login(common.DB, 'admin', token)
+ self.assertTrue(res)
+
+ @unittest.skipIf(os.environ.get('TRAVIS'),
+ 'When run by travis, tests runs on a database with all required addons from server-tools and '
+ 'their dependencies installed. Even if `auth_from_http_remote_user` does not require the `mail`'
+ 'module, The previous installation of the mail module has created the column '
+ '`notification_email_send` as REQUIRED into the table res_partner. BTW, it\'s no more possible '
+ 'to copy a res_user without an intefirty error')
+ def test_copy(self):
+ '''Check that the sso_key is not copied on copy
+ '''
+ res_users_obj = self.registry('res.users')
+ vals = {'sso_key': '123'}
+ res_users_obj.write(self.cr, self.uid, self.uid, vals)
+ read_vals = res_users_obj.read(self.cr, self.uid, self.uid, ['sso_key'])
+ self.assertDictContainsSubset(vals, read_vals)
+ copy = res_users_obj.copy(self.cr, self.uid, self.uid)
+ read_vals = res_users_obj.read(self.cr, self.uid, copy, ['sso_key'])
+ self.assertFalse(read_vals.get('sso_key'))
diff --git a/auth_from_http_remote_user/utils.py b/auth_from_http_remote_user/utils.py
new file mode 100644
index 000000000..ee1eacf68
--- /dev/null
+++ b/auth_from_http_remote_user/utils.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Laurent Mignon
+# Copyright 2014 'ACSONE SA/NV'
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+KEY_LENGTH = 16