Browse Source

Merge pull request #580 from LasLabs/release/10.0/auth_session_timeout

[MIG][10.0] auth_session_timeout
pull/1044/head
Pedro M. Baeza 7 years ago
committed by GitHub
parent
commit
cbf074750f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .travis.yml
  2. 42
      auth_session_timeout/README.rst
  3. 1
      auth_session_timeout/__init__.py
  4. 19
      auth_session_timeout/__manifest__.py
  5. 7
      auth_session_timeout/data/ir_config_parameter_data.xml
  6. 1
      auth_session_timeout/models/__init__.py
  7. 44
      auth_session_timeout/models/ir_config_parameter.py
  8. 117
      auth_session_timeout/models/res_users.py
  9. 4
      auth_session_timeout/tests/__init__.py
  10. 66
      auth_session_timeout/tests/test_ir_config_parameter.py
  11. 108
      auth_session_timeout/tests/test_res_users.py
  12. 4
      base_external_system/tests/common.py

6
.travis.yml

@ -24,10 +24,12 @@ env:
matrix:
- LINT_CHECK="1"
- TRANSIFEX="1"
- TESTS="1" ODOO_REPO="odoo/odoo" EXCLUDE="database_cleanup"
- TESTS="1" ODOO_REPO="OCA/OCB" EXCLUDE="database_cleanup"
- TESTS="1" ODOO_REPO="odoo/odoo" EXCLUDE="database_cleanup,auth_session_timeout"
- TESTS="1" ODOO_REPO="OCA/OCB" EXCLUDE="database_cleanup,auth_session_timeout"
- TESTS="1" ODOO_REPO="odoo/odoo" INCLUDE="database_cleanup"
- TESTS="1" ODOO_REPO="OCA/OCB" INCLUDE="database_cleanup"
- TESTS="1" ODOO_REPO="odoo/odoo" INCLUDE="auth_session_timeout"
- TESTS="1" ODOO_REPO="OCA/OCB" INCLUDE="auth_session_timeout"
virtualenv:
system_site_packages: true

42
auth_session_timeout/README.rst

@ -1,6 +1,8 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
=========================
Inactive Sessions Timeout
=========================
@ -13,16 +15,32 @@ Configuration
Two system parameters are available:
* inactive_session_time_out_delay: validity of a session in seconds (default = 2 Hours)
* inactive_session_time_out_ignored_url: technical urls where the check does not occur
* ``inactive_session_time_out_delay``: validity of a session in seconds
(default = 2 Hours)
* ``inactive_session_time_out_ignored_url``: technical urls where the check
does not occur
Usage
=====
Setup the session parameters as described above.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/9.0
Known issues / Roadmap
======================
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20auth_session_timeout%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
@ -32,16 +50,20 @@ Contributors
* Cédric Pigeon <cedric.pigeon@acsone.eu>
* Dhinesh D <dvdhinesh.mail@gmail.com>
* Jesse Morgan <jmorgan.nz@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://odoo-community.org
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.
To contribute to this module, please visit https://odoo-community.org.

1
auth_session_timeout/__init__.py

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2015 ACSONE SA/NV, Dhinesh D
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models

19
auth_session_timeout/__manifest__.py

@ -1,28 +1,23 @@
# -*- coding: utf-8 -*-
# (c) 2015 ACSONE SA/NV, Dhinesh D
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': "Inactive Sessions Timeout",
'summary': """
This module disable all inactive sessions since a given delay""",
'author': "ACSONE SA/NV, Dhinesh D, Odoo Community Association (OCA)",
'author': "ACSONE SA/NV, "
"Dhinesh D, "
"Jesse Morgan, "
"LasLabs, "
"Odoo Community Association (OCA)",
'maintainer': 'Odoo Community Association (OCA)',
'website': "http://acsone.eu",
'category': 'Tools',
'version': '9.0.1.0.0',
'version': '10.0.1.0.0',
'license': 'AGPL-3',
'depends': [
'base',
],
'data': [
'data/ir_config_parameter_data.xml'
],
'installable': False,
'installable': True,
}

7
auth_session_timeout/data/ir_config_parameter_data.xml

@ -4,18 +4,13 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<data noupdate="1">
<odoo noupdate="1">
<record id="inactive_session_time_out_delay" model="ir.config_parameter">
<field name="key">inactive_session_time_out_delay</field>
<field name="value">7200</field>
</record>
</data>
<data noupdate="1">
<record id="inactive_session_time_out_ignored_url" model="ir.config_parameter">
<field name="key">inactive_session_time_out_ignored_url</field>
<field name="value">/calendar/notify,/longpolling/poll</field>
</record>
</data>
</odoo>

1
auth_session_timeout/models/__init__.py

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2015 ACSONE SA/NV, Dhinesh D
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import res_users

44
auth_session_timeout/models/ir_config_parameter.py

@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
# (c) 2015 ACSONE SA/NV, Dhinesh D
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, api, tools, SUPERUSER_ID
from odoo import api, models, tools
DELAY_KEY = 'inactive_session_time_out_delay'
IGNORED_PATH_KEY = 'inactive_session_time_out_ignored_url'
@ -13,24 +11,30 @@ IGNORED_PATH_KEY = 'inactive_session_time_out_ignored_url'
class IrConfigParameter(models.Model):
_inherit = 'ir.config_parameter'
@tools.ormcache(skiparg=0)
def get_session_parameters(self, db):
param_model = self.pool['ir.config_parameter']
cr = self.pool.cursor()
delay = False
urls = []
try:
delay = int(param_model.get_param(
cr, SUPERUSER_ID, DELAY_KEY, 7200))
urls = param_model.get_param(
cr, SUPERUSER_ID, IGNORED_PATH_KEY, '').split(',')
finally:
cr.close()
return delay, urls
@api.model
@tools.ormcache('self.env.cr.dbname')
def _auth_timeout_get_parameter_delay(self):
return int(
self.env['ir.config_parameter'].sudo().get_param(
DELAY_KEY, 7200,
)
)
@api.model
@tools.ormcache('self.env.cr.dbname')
def _auth_timeout_get_parameter_ignored_urls(self):
urls = self.env['ir.config_parameter'].sudo().get_param(
IGNORED_PATH_KEY, '',
)
return urls.split(',')
@api.multi
def write(self, vals, context=None):
def write(self, vals):
res = super(IrConfigParameter, self).write(vals)
if self.key in [DELAY_KEY, IGNORED_PATH_KEY]:
self.get_session_parameters.clear_cache(self)
self._auth_timeout_get_parameter_delay.clear_cache(
self.filtered(lambda r: r.key == DELAY_KEY),
)
self._auth_timeout_get_parameter_ignored_urls.clear_cache(
self.filtered(lambda r: r.key == IGNORED_PATH_KEY),
)
return res

117
auth_session_timeout/models/res_users.py

@ -3,42 +3,107 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models
from openerp import http
import logging
from openerp.http import root
from openerp.http import request
from os import utime
from os.path import getmtime
from time import time
from os import utime
from odoo import api, http, models
_logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
def _check_session_validity(self, db, uid, passwd):
if not request:
return
session = request.session
session_store = root.session_store
param_obj = self.pool['ir.config_parameter']
delay, urls = param_obj.get_session_parameters(db)
deadline = time() - delay
path = session_store.get_session_filename(session.sid)
try:
if getmtime(path) < deadline:
@api.model_cr_context
def _auth_timeout_get_ignored_urls(self):
"""Pluggable method for calculating ignored urls
Defaults to stored config param
"""
params = self.env['ir.config_parameter']
return params._auth_timeout_get_parameter_ignored_urls()
@api.model_cr_context
def _auth_timeout_deadline_calculate(self):
"""Pluggable method for calculating timeout deadline
Defaults to current time minus delay using delay stored as config
param.
"""
params = self.env['ir.config_parameter']
delay = params._auth_timeout_get_parameter_delay()
if delay <= 0:
return False
return time() - delay
@api.model_cr_context
def _auth_timeout_session_terminate(self, session):
"""Pluggable method for terminating a timed-out session
This is a late stage where a session timeout can be aborted.
Useful if you want to do some heavy checking, as it won't be
called unless the session inactivity deadline has been reached.
Return:
True: session terminated
False: session timeout cancelled
"""
if session.db and session.uid:
session.logout(keep_db=True)
elif http.request.httprequest.path not in urls:
# the session is not expired, update the last modification
# and access time.
utime(path, None)
except OSError:
pass
return True
@api.model_cr_context
def _auth_timeout_check(self):
"""Perform session timeout validation and expire if needed."""
if not http.request:
return
session = http.request.session
# Calculate deadline
deadline = self._auth_timeout_deadline_calculate()
# Check if past deadline
expired = False
if deadline is not False:
path = http.root.session_store.get_session_filename(session.sid)
try:
expired = getmtime(path) < deadline
except OSError as e:
_logger.exception(
'Exception reading session file modified time.',
)
# Force expire the session. Will be resolved with new session.
expired = True
# Try to terminate the session
terminated = False
if expired:
terminated = self._auth_timeout_session_terminate(session)
# If session terminated, all done
if terminated:
return
def check(self, db, uid, passwd):
res = super(ResUsers, self).check(db, uid, passwd)
self._check_session_validity(db, uid, passwd)
# Else, conditionally update session modified and access times
ignored_urls = self._auth_timeout_get_ignored_urls()
if http.request.http.request.path not in ignored_urls:
if 'path' not in locals():
path = http.root.session_store.get_session_filename(
session.sid,
)
try:
utime(path, None)
except OSError as e:
_logger.exception(
'Exception updating session file access/modified times.',
)
@classmethod
def check(cls, *args, **kwargs):
res = super(ResUsers, cls).check(*args, **kwargs)
http.request.env.user._auth_timeout_check()
return res

4
auth_session_timeout/tests/__init__.py

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# (c) 2015 ACSONE SA/NV, Dhinesh D
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_ir_config_parameter
from . import test_res_users

66
auth_session_timeout/tests/test_ir_config_parameter.py

@ -3,26 +3,74 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import threading
from openerp.tests import common
import openerp
from odoo.tests import common
class TestIrConfigParameter(common.TransactionCase):
def setUp(self):
super(TestIrConfigParameter, self).setUp()
self.db = openerp.tools.config['db_name']
if not self.db and hasattr(threading.current_thread(), 'dbname'):
self.db = threading.current_thread().dbname
self.db = self.env.cr.dbname
self.param_obj = self.env['ir.config_parameter']
self.data_obj = self.env['ir.model.data']
self.delay = self.env.ref(
'auth_session_timeout.inactive_session_time_out_delay')
def test_check_delay(self):
delay, urls = self.param_obj.get_session_parameters(self.db)
def test_check_session_param_delay(self):
delay = self.param_obj._auth_timeout_get_parameter_delay()
self.assertEqual(delay, int(self.delay.value))
self.assertIsInstance(delay, int)
def test_check_session_param_urls(self):
urls = self.param_obj._auth_timeout_get_parameter_ignored_urls()
self.assertIsInstance(urls, list)
class TestIrConfigParameterCaching(common.TransactionCase):
def setUp(self):
super(TestIrConfigParameterCaching, self).setUp()
self.db = self.env.cr.dbname
self.param_obj = self.env['ir.config_parameter']
self.get_param_called = False
test = self
def get_param(*args, **kwargs):
test.get_param_called = True
return orig_get_param(*args[1:], **kwargs)
orig_get_param = self.param_obj.get_param
self.param_obj._patch_method(
'get_param',
get_param)
def tearDown(self):
super(TestIrConfigParameterCaching, self).tearDown()
self.param_obj._revert_method('get_param')
def test_auth_timeout_get_parameter_delay_cache(self):
"""It should cache the parameter call."""
self.get_param_called = False
self.param_obj._auth_timeout_get_parameter_delay()
self.assertTrue(self.get_param_called)
def test_auth_timeout_get_parameter_ignored_urls_cache(self):
"""It should cache the parameter call."""
self.get_param_called = False
self.param_obj._auth_timeout_get_parameter_ignored_urls()
self.assertTrue(self.get_param_called)
def test_check_param_writes_clear_delay_cache(self):
self.param_obj._auth_timeout_get_parameter_delay()
self.get_param_called = False
self.param_obj.set_param('inactive_session_time_out_delay', 7201)
self.param_obj._auth_timeout_get_parameter_delay()
self.assertTrue(self.get_param_called)
def test_check_param_writes_clear_ignore_url_cache(self):
self.param_obj._auth_timeout_get_parameter_ignored_urls()
self.get_param_called = False
self.param_obj.set_param('inactive_session_time_out_ignored_url',
'example.com')
self.param_obj._auth_timeout_get_parameter_ignored_urls()
self.assertTrue(self.get_param_called)

108
auth_session_timeout/tests/test_res_users.py

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import mock
from contextlib import contextmanager
from odoo.tools.misc import mute_logger
from odoo.tests.common import TransactionCase
class EndTestException(Exception):
""" It stops tests from continuing """
pass
class TestResUsers(TransactionCase):
def setUp(self):
super(TestResUsers, self).setUp()
self.ResUsers = self.env['res.users']
@contextmanager
def _mock_assets(self, assets=None):
""" It provides mocked imports from res_users.py
:param assets: (list) Name of imports to mock. Mocks `http` if None
:return: (dict) Dictionary of mocks, keyed by module name
"""
if assets is None:
assets = ['http']
patches = {name: mock.DEFAULT for name in assets}
with mock.patch.multiple(
'odoo.addons.auth_session_timeout.models.res_users', **patches
) as mocks:
yield mocks
def _auth_timeout_check(self, http_mock):
""" It wraps ``_auth_timeout_check`` for easier calling """
self.db = mock.MagicMock()
self.uid = mock.MagicMock()
self.passwd = mock.MagicMock()
self.path = '/this/is/a/test/path'
get_filename = http_mock.root.session_store.get_session_filename
get_filename.return_value = self.path
return self.ResUsers._auth_timeout_check()
def test_session_validity_no_request(self):
""" It should return immediately if no request """
with self._mock_assets() as assets:
assets['http'].request = False
res = self._auth_timeout_check(assets['http'])
self.assertFalse(res)
def test_session_validity_gets_session_file(self):
""" It should call get the session file for the session id """
with self._mock_assets() as assets:
get_params = assets['http'].request.env[''].get_session_parameters
get_params.return_value = 0, []
store = assets['http'].root.session_store
store.get_session_filename.side_effect = EndTestException
with self.assertRaises(EndTestException):
self._auth_timeout_check(assets['http'])
store.get_session_filename.assert_called_once_with(
assets['http'].request.session.sid,
)
def test_session_validity_logout(self):
""" It should log out of session if past deadline """
with self._mock_assets(['http', 'getmtime', 'utime']) as assets:
get_params = assets['http'].request.env[''].get_session_parameters
get_params.return_value = -9999, []
assets['getmtime'].return_value = 0
self._auth_timeout_check(assets['http'])
assets['http'].request.session.logout.assert_called_once_with(
keep_db=True,
)
def test_session_validity_updates_utime(self):
""" It should update utime of session file if not expired """
with self._mock_assets(['http', 'getmtime', 'utime']) as assets:
get_params = assets['http'].request.env[''].get_session_parameters
get_params.return_value = 9999, []
self._auth_timeout_check(assets['http'])
assets['utime'].assert_called_once_with(
assets['http'].root.session_store.get_session_filename(),
None,
)
@mute_logger('odoo.addons.auth_session_timeout.models.res_users')
def test_session_validity_os_error_guard(self):
""" It should properly guard from OSError & return """
with self._mock_assets(['http', 'utime', 'getmtime']) as assets:
get_params = assets['http'].request.env[''].get_session_parameters
get_params.return_value = 0, []
assets['getmtime'].side_effect = OSError
res = self._auth_timeout_check(assets['http'])
self.assertFalse(res)
def test_on_timeout_session_loggedout(self):
with self._mock_assets(['http', 'getmtime']) as assets:
assets['getmtime'].return_value = 0
assets['http'].request.session.uid = self.env.uid
assets['http'].request.session.dbname = self.env.cr.dbname
assets['http'].request.session.sid = 123
assets['http'].request.session.logout = mock.Mock()
self.ResUsers._auth_timeout_check()
self.assertTrue(assets['http'].request.session.logout.called)

4
base_external_system/tests/common.py

@ -5,10 +5,10 @@
from contextlib import contextmanager
from mock import MagicMock
from odoo.tests.common import TransactionCase
from odoo.tests.common import HttpCase
class Common(TransactionCase):
class Common(HttpCase):
@contextmanager
def _mock_method(self, method_name, method_obj=None):

Loading…
Cancel
Save