changeset e4bfb7718b16 in tryton:default details: https://hg.tryton.org/tryton?cmd=changeset;node=e4bfb7718b16 description: Protect trusted devices against brute force attack
issue9386 review321511002 diffstat: CHANGELOG | 1 + tryton/device_cookie.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ tryton/rpc.py | 4 +- 3 files changed, 80 insertions(+), 1 deletions(-) diffs (116 lines): diff -r 6bca8daa4d9c -r e4bfb7718b16 CHANGELOG --- a/CHANGELOG Sat Feb 20 00:53:18 2021 +0100 +++ b/CHANGELOG Sun Feb 21 16:23:11 2021 +0100 @@ -1,3 +1,4 @@ +* Handle device cookie * Add breadcrumb as title of window form * Display revision on dialog * Execute report asynchronously diff -r 6bca8daa4d9c -r e4bfb7718b16 tryton/device_cookie.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tryton/device_cookie.py Sun Feb 21 16:23:11 2021 +0100 @@ -0,0 +1,76 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +import os +import json +import logging +from threading import Lock + +from tryton.config import get_config_dir, CONFIG + +logger = logging.getLogger(__name__) +COOKIES_PATH = os.path.join(get_config_dir(), 'device_cookies') + + +_lock = Lock() + + +def renew(): + from tryton.common import common + + def set_cookie(new_cookie): + try: + new_cookie = new_cookie() + except Exception: + logger.error("Cannot renew device cookie", exc_info=True) + else: + _set(new_cookie) + + current_cookie = get() + common.RPCExecute( + 'model', 'res.user.device', 'renew', current_cookie, + process_exception=False, callback=set_cookie) + + +def get(): + cookies = _load() + return cookies.get(_key()) + + +def _key(): + from tryton import common + + host = CONFIG['login.host'] + hostname = common.get_hostname(host) + port = common.get_port(host) + database = CONFIG['login.db'] + username = CONFIG['login.login'] + + return '%(username)s@%(hostname)s:%(port)s/%(database)s' % { + 'username': username, + 'hostname': hostname, + 'port': port, + 'database': database, + } + + +def _set(cookie): + cookies = _load() + cookies[_key()] = cookie + try: + with _lock: + with open(COOKIES_PATH, 'w') as cookies_file: + json.dump(cookies, cookies_file) + except Exception: + logger.error('Unable to save cookies file') + + +def _load(): + if not os.path.isfile(COOKIES_PATH): + return {} + try: + with open(COOKIES_PATH) as cookies: + cookies = json.load(cookies) + except Exception: + logger.error("Unable to load device cookies file", exc_info=True) + cookies = {} + return cookies diff -r 6bca8daa4d9c -r e4bfb7718b16 tryton/rpc.py --- a/tryton/rpc.py Sat Feb 20 00:53:18 2021 +0100 +++ b/tryton/rpc.py Sun Feb 21 16:23:11 2021 +0100 @@ -11,7 +11,7 @@ from functools import partial -from tryton import bus +from tryton import bus, device_cookies from tryton.jsonrpc import ServerProxy, ServerPool, Fault from tryton.fingerprints import Fingerprints from tryton.config import get_config_dir @@ -80,6 +80,7 @@ database = CONFIG['login.db'] username = CONFIG['login.login'] language = CONFIG['client.lang'] + parameters['device_cookie'] = device_cookies.get() connection = ServerProxy(hostname, port, database) logging.getLogger(__name__).info('common.db.login(%s, %s, %s)' % (username, 'x' * 10, language)) @@ -91,6 +92,7 @@ CONNECTION.close() CONNECTION = ServerPool( hostname, port, database, session=session, cache=not CONFIG['dev']) + device_cookies.renew() bus.listen(CONNECTION)