diff --git a/auth_from_http_remote_user/__init__.py b/auth_from_http_remote_user/__init__.py
new file mode 100644
index 000000000..7fb1b9d59
--- /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_users
+from . import model
diff --git a/auth_from_http_remote_user/__openerp__.py b/auth_from_http_remote_user/__openerp__.py
new file mode 100644
index 000000000..d4e9731c2
--- /dev/null
+++ b/auth_from_http_remote_user/__openerp__.py
@@ -0,0 +1,162 @@
+# -*- 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.
+To be active, the module must be installed in the expected databases and loaded
+at startup; Add the *--load* parameter to the startup command: ::
+
+ --load=web,web_kanban,auth_from_http_remote_user, ...
+
+If the field is found in the header and no user matches the given one, the
+system issue a login error page. (*401* `Unauthorized`)
+
+Use case.
+---------
+
+The module allows integration with external security systems [#]_ that can pass
+along authentication of a user via Remote_User HTTP header field. In many
+cases, this is achieved via server like Apache HTTPD or nginx proxying Odoo.
+
+.. important:: When proxying your Odoo server with Apache or nginx, It's
+ important to filter out the Remote_User HTTP header field before your
+ request is processed by the proxy to avoid security issues. In apache you
+ can do it by using the RequestHeader directive in your VirtualHost
+ section ::
+
+
+ ServerName MY_VHOST.com
+ ProxyRequests Off
+ ...
+
+ RequestHeader unset Remote-User early
+ ProxyPass / http://127.0.0.1:8069/ retry=10
+ ProxyPassReverse / http://127.0.0.1:8069/
+ ProxyPreserveHost On
+
+
+
+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 Odoo 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
+
+
+ RequestHeader unset Remote-User early
+ 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 Odoo, 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 Odoo, 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 Odoo and are automatically
+logged in the system.
+
+.. [#] Shibolleth, Tivoli access manager, ..
+.. [#] Based on a ubuntu 12.04 env
+
+""",
+ 'author': 'Acsone SA/NV',
+ 'maintainer': 'ACSONE SA/NV',
+ 'website': 'http://www.acsone.eu',
+ 'depends': ['base', 'web', 'base_setup'],
+ "license": "AGPL-3",
+ 'data': [],
+ "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..6b12b8082
--- /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 main
diff --git a/auth_from_http_remote_user/controllers/main.py b/auth_from_http_remote_user/controllers/main.py
new file mode 100644
index 000000000..85859619a
--- /dev/null
+++ b/auth_from_http_remote_user/controllers/main.py
@@ -0,0 +1,110 @@
+# -*- 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
+
+import openerp
+from openerp import http
+from openerp.http import request
+from openerp.addons.web.controllers import main
+from openerp.addons.auth_from_http_remote_user.model import \
+ AuthFromHttpRemoteUserInstalled
+from .. import utils
+
+import random
+import logging
+import werkzeug
+
+_logger = logging.getLogger(__name__)
+
+
+class Home(main.Home):
+
+ _REMOTE_USER_ATTRIBUTE = 'HTTP_REMOTE_USER'
+
+ @http.route('/web', type='http', auth="none")
+ def web_client(self, s_action=None, **kw):
+ main.ensure_db()
+ try:
+ self._bind_http_remote_user(http.request.session.db)
+ except http.AuthenticationError:
+ return werkzeug.exceptions.Unauthorized().get_response()
+ return super(Home, self).web_client(s_action, **kw)
+
+ def _search_user(self, res_users, login, cr):
+ 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 _bind_http_remote_user(self, db_name):
+ try:
+ registry = openerp.registry(db_name)
+ with registry.cursor() as cr:
+ if AuthFromHttpRemoteUserInstalled._name not in registry:
+ # module not installed in database,
+ # continue usual behavior
+ return
+
+ headers = http.request.httprequest.headers.environ
+
+ login = headers.get(self._REMOTE_USER_ATTRIBUTE, None)
+ if not login:
+ # no HTTP_REMOTE_USER header,
+ # continue usual behavior
+ return
+
+ request_login = request.session.login
+ if request_login:
+ if request_login == login:
+ # already authenticated
+ return
+ else:
+ request.session.logout(keep_db=True)
+
+ res_users = registry.get('res.users')
+ user_id = self._search_user(res_users, login, cr)
+ if not user_id:
+ # HTTP_REMOTE_USER login not found in database
+ request.session.logout(keep_db=True)
+ raise http.AuthenticationError()
+
+ # generate a specific key for authentication
+ key = randomString(utils.KEY_LENGTH, '0123456789abcdef')
+ res_users.write(cr, SUPERUSER_ID, [user_id], {'sso_key': key})
+ request.session.authenticate(db_name, login=login,
+ password=key, uid=user_id)
+ except http.AuthenticationError, e:
+ raise e
+ except Exception, e:
+ _logger.error("Error binding Http Remote User session",
+ exc_info=True)
+ raise e
+
+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/model.py b/auth_from_http_remote_user/model.py
new file mode 100644
index 000000000..c4ca50a33
--- /dev/null
+++ b/auth_from_http_remote_user/model.py
@@ -0,0 +1,27 @@
+# -*- 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
+
+
+class AuthFromHttpRemoteUserInstalled(orm.AbstractModel):
+ """An abstract model used to safely know if the module is installed
+ """
+ _name = 'auth_from_http_remote_user.installed'
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..638179405
--- /dev/null
+++ b/auth_from_http_remote_user/res_users.py
@@ -0,0 +1,65 @@
+# -*- 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/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..43d5481c6
--- /dev/null
+++ b/auth_from_http_remote_user/tests/test_res_users.py
@@ -0,0 +1,90 @@
+# -*- 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')
+ res = res_users_obj.authenticate(common.DB, 'admin', 'admin', None)
+ uid = res
+ self.assertTrue(res, "Basic login must works as expected")
+ token = "123456"
+ res = res_users_obj.authenticate(common.DB, 'admin', token, None)
+ 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.authenticate(common.DB, 'admin', token, None)
+ 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