[IMP] http: Sessions implicit deactivation

- Store a token inside sessions to allow implicit session deactivation when needed.
This commit is contained in:
tbe-odoo
2018-03-19 18:11:12 +01:00
committed by Toufik Benjaa
parent caf6b4a7c7
commit da1f153d61
6 changed files with 67 additions and 4 deletions
+3
View File
@@ -96,3 +96,6 @@ class ResUsers(models.Model):
internally internally
""" """
return default_crypt_context return default_crypt_context
def _get_session_token_fields(self):
return super(ResUsers, self)._get_session_token_fields() | {'password_crypt'}
+3
View File
@@ -125,3 +125,6 @@ class ResUsers(models.Model):
res = self.sudo().search([('id', '=', self.env.uid), ('oauth_access_token', '=', password)]) res = self.sudo().search([('id', '=', self.env.uid), ('oauth_access_token', '=', password)])
if not res: if not res:
raise raise
def _get_session_token_fields(self):
return super(ResUsers, self)._get_session_token_fields() | {'oauth_access_token'}
+33
View File
@@ -3,11 +3,13 @@
import pytz import pytz
import datetime import datetime
import logging import logging
import hmac
from collections import defaultdict from collections import defaultdict
from itertools import chain, repeat from itertools import chain, repeat
from lxml import etree from lxml import etree
from lxml.builder import E from lxml.builder import E
from hashlib import sha256
from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError
@@ -375,6 +377,8 @@ class Users(models.Model):
db = self._cr.dbname db = self._cr.dbname
for id in self.ids: for id in self.ids:
self.__uid_cache[db].pop(id, None) self.__uid_cache[db].pop(id, None)
if any(key in values for key in self._get_session_token_fields()):
self._invalidate_session_cache()
return res return res
@@ -385,6 +389,7 @@ class Users(models.Model):
db = self._cr.dbname db = self._cr.dbname
for id in self.ids: for id in self.ids:
self.__uid_cache[db].pop(id, None) self.__uid_cache[db].pop(id, None)
self._invalidate_session_cache()
return super(Users, self).unlink() return super(Users, self).unlink()
@api.model @api.model
@@ -510,6 +515,34 @@ class Users(models.Model):
finally: finally:
cr.close() cr.close()
def _get_session_token_fields(self):
return {'id', 'login', 'password', 'active'}
@tools.ormcache('sid')
def _compute_session_token(self, sid):
""" Compute a session token given a session id and a user id """
# retrieve the fields used to generate the session token
session_fields = ', '.join(sorted(self._get_session_token_fields()))
self.env.cr.execute("""SELECT %s, (SELECT value FROM ir_config_parameter WHERE key='database.secret')
FROM res_users
WHERE id=%%s""" % (session_fields), (self.id,))
if self.env.cr.rowcount != 1:
self._invalidate_session_cache()
return False
data_fields = self.env.cr.fetchone()
# generate hmac key
key = (u'%s' % (data_fields,)).encode('utf-8')
# hmac the session id
data = sid.encode('utf-8')
h = hmac.new(key, data, sha256)
# keep in the cache the token
return h.hexdigest()
@api.multi
def _invalidate_session_cache(self):
""" Clear the sessions cache """
self._compute_session_token.clear_cache(self)
@api.model @api.model
def change_password(self, old_passwd, new_passwd): def change_password(self, old_passwd, new_passwd):
"""Change current user password. Old password must be provided explicitly """Change current user password. Old password must be provided explicitly
+12 -3
View File
@@ -1046,7 +1046,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.db = db self.db = db
self.uid = uid self.uid = uid
self.login = login self.login = login
self.password = password self.session_token = uid and security.compute_session_token(self)
request.uid = uid request.uid = uid
request.disable_db = False request.disable_db = False
@@ -1061,7 +1061,16 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
""" """
if not self.db or not self.uid: if not self.db or not self.uid:
raise SessionExpiredException("Session expired") raise SessionExpiredException("Session expired")
security.check(self.db, self.uid, self.password)
# == BACKWARD COMPATIBILITY TO CONVERT OLD SESSION TYPE TO THE NEW ONES ! REMOVE ME AFTER 11.0 ==
if self.get('password'):
security.check(self.db, self.uid, self.password)
self.session_token = security.compute_session_token(self)
self.pop('password')
# =================================================================================================
# here we check if the session is still valid
if not security.check_session(self):
raise SessionExpiredException("Session expired")
def logout(self, keep_db=False): def logout(self, keep_db=False):
for k in self.keys(): for k in self.keys():
@@ -1074,7 +1083,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.setdefault("db", None) self.setdefault("db", None)
self.setdefault("uid", None) self.setdefault("uid", None)
self.setdefault("login", None) self.setdefault("login", None)
self.setdefault("password", None) self.setdefault("session_token", None)
self.setdefault("context", {}) self.setdefault("context", {})
def get_context(self): def get_context(self):
+13
View File
@@ -11,3 +11,16 @@ def login(db, login, password):
def check(db, uid, passwd): def check(db, uid, passwd):
res_users = odoo.registry(db)['res.users'] res_users = odoo.registry(db)['res.users']
return res_users.check(db, uid, passwd) return res_users.check(db, uid, passwd)
def compute_session_token(session):
with odoo.registry(session.db).cursor() as cr:
self = odoo.api.Environment(cr, session.uid, {})['res.users'].browse(session.uid)
return self._compute_session_token(session.sid)
def check_session(session):
with odoo.registry(session.db).cursor() as cr:
self = odoo.api.Environment(cr, session.uid, {})['res.users'].browse(session.uid)
if odoo.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token):
return True
self._invalidate_session_cache()
return False
+3 -1
View File
@@ -26,6 +26,8 @@ import werkzeug
import odoo import odoo
from odoo import api from odoo import api
from odoo.service import security
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -286,7 +288,7 @@ class HttpCase(TransactionCase):
session.db = db session.db = db
session.uid = uid session.uid = uid
session.login = user session.login = user
session.password = password session.session_token = uid and security.compute_session_token(session)
session.context = env['res.users'].context_get() or {} session.context = env['res.users'].context_get() or {}
session.context['uid'] = uid session.context['uid'] = uid
session._fix_lang(session.context) session._fix_lang(session.context)