Browse Source

Merge pull request #1168 from acsone/9.0-keychain-caching

[9.0] Keychain account caching
pull/1563/head
Pedro M. Baeza 6 years ago
committed by GitHub
parent
commit
7a2fc40825
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      keychain/README.rst
  2. 1
      keychain/models/__init__.py
  3. 2
      keychain/models/keychain.py
  4. 100
      keychain/models/keychain_backend.py
  5. 1
      keychain/tests/__init__.py
  6. 18
      keychain/tests/test_keychain.py
  7. 81
      keychain/tests/test_keychain_backend.py
  8. 4
      keychain/views/keychain_view.xml

5
keychain/README.rst

@ -209,6 +209,9 @@ help us smashing it by providing a detailed and welcomed feedback.
Credits Credits
======= =======
* `Akretion <https://akretion.com>`_
Contributors Contributors
------------ ------------
@ -219,7 +222,7 @@ Funders
The development of this module has been financially supported by: The development of this module has been financially supported by:
* `Akretion <https://akretion.com>`_
* Akretion
Maintainer Maintainer
---------- ----------

1
keychain/models/__init__.py

@ -1 +1,2 @@
from . import keychain from . import keychain
from . import keychain_backend

2
keychain/models/keychain.py

@ -60,7 +60,7 @@ class KeychainAccount(models.Model):
# Only needed in v8 for _description_searchable issues # Only needed in v8 for _description_searchable issues
return True return True
def get_password(self):
def _get_password(self):
"""Password in clear text.""" """Password in clear text."""
try: try:
return self._decode_password(self.password) return self._decode_password(self.password)

100
keychain/models/keychain_backend.py

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion Sebastien Beau
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
from openerp.tools.config import config
class KeychainBackend(models.AbstractModel):
_name = 'keychain.backend'
_backend_name = None
name = fields.Char(required=True)
password = fields.Char(
compute="_compute_password",
inverse="_inverse_password",
required=True)
data = fields.Serialized(
compute="_compute_keychain",
inverse="_inverse_keychain",
help="Additionnal data as json")
keychain_account_id = fields.Many2one(
'keychain.account', compute='_compute_keychain_account')
@api.multi
def _compute_keychain_account(self):
for backend in self:
backend.keychain_account_id = backend._get_existing_keychain()
@api.multi
def _get_technical_name(self):
self.ensure_one()
return '%s,%s' % (self._name, self.id)
@api.multi
def _get_existing_keychain(self):
self.ensure_one()
return self.env['keychain.account'].retrieve([
('namespace', '=', self._backend_name),
('technical_name', '=', self._get_technical_name())
])
@api.multi
def _prepare_keychain(self):
self.ensure_one()
env = config.get('running_env')
return {
'name': "%s %s" % (self.name, env),
'technical_name': self._get_technical_name(),
'namespace': self._backend_name,
'environment': env,
}
@api.multi
def _get_keychain_account(self):
self.ensure_one()
if not self.keychain_account_id:
vals = self._prepare_keychain()
self.keychain_account_id = self.env['keychain.account'].create(
vals)
return self.keychain_account_id
@api.multi
def _inverse_password(self):
for record in self:
account = self._get_keychain_account()
if record.password and record.password != '******':
account.clear_password = record.password
@api.multi
def _compute_password(self):
for record in self:
account = record._get_existing_keychain()
if account and account.password:
record.password = "******"
else:
record.password = ""
@api.multi
def _inverse_keychain(self):
for record in self:
account = record._get_keychain_account()
account.data = account._serialize_data(record.data)
@api.multi
def _compute_keychain(self):
for record in self:
account = record._get_existing_keychain()
if account:
record.data = account.get_data()
else:
record.data = {}
@api.multi
def _get_password(self):
self.ensure_one()
if self.keychain_account_id:
return self.keychain_account_id._get_password()
else:
return False

1
keychain/tests/__init__.py

@ -1 +1,2 @@
from . import test_keychain from . import test_keychain
from . import test_keychain_backend

18
keychain/tests/test_keychain.py

@ -22,7 +22,7 @@ class TestKeychain(TransactionCase):
self.keychain = self.env['keychain.account'] self.keychain = self.env['keychain.account']
config['keychain_key'] = Fernet.generate_key() config['keychain_key'] = Fernet.generate_key()
self.old_running_env = config['running_env']
self.old_running_env = config.get('running_env', '')
config['running_env'] = None config['running_env'] = None
def _init_data(self): def _init_data(self):
@ -65,7 +65,7 @@ class TestKeychain(TransactionCase):
account.clear_password = password account.clear_password = password
account._inverse_set_password() account._inverse_set_password()
self.assertTrue(account.clear_password != account.password) self.assertTrue(account.clear_password != account.password)
self.assertEqual(account.get_password(), password)
self.assertEqual(account._get_password(), password)
def test_wrong_key(self): def test_wrong_key(self):
"""It should raise an exception when encoded key != decoded.""" """It should raise an exception when encoded key != decoded."""
@ -75,7 +75,7 @@ class TestKeychain(TransactionCase):
account._inverse_set_password() account._inverse_set_password()
config['keychain_key'] = Fernet.generate_key() config['keychain_key'] = Fernet.generate_key()
try: try:
account.get_password()
account._get_password()
self.assertTrue(False, 'It should not work with another key') self.assertTrue(False, 'It should not work with another key')
except Warning as err: except Warning as err:
self.assertTrue(True, 'It should raise a Warning') self.assertTrue(True, 'It should raise a Warning')
@ -131,13 +131,13 @@ class TestKeychain(TransactionCase):
account.clear_password = 'abc' account.clear_password = 'abc'
account._inverse_set_password() account._inverse_set_password()
self.assertEqual( self.assertEqual(
account.get_password(),
account._get_password(),
'abc', 'Should work with dev') 'abc', 'Should work with dev')
config['running_env'] = 'prod' config['running_env'] = 'prod'
with self.assertRaises(Warning): with self.assertRaises(Warning):
self.assertEqual( self.assertEqual(
account.get_password(),
account._get_password(),
'abc', 'Should not work with prod key') 'abc', 'Should not work with prod key')
def test_multienv_blank(self): def test_multienv_blank(self):
@ -151,12 +151,12 @@ class TestKeychain(TransactionCase):
account.clear_password = 'abc' account.clear_password = 'abc'
account._inverse_set_password() account._inverse_set_password()
self.assertEqual( self.assertEqual(
account.get_password(),
account._get_password(),
'abc', 'Should work with dev') 'abc', 'Should work with dev')
config['running_env'] = 'prod' config['running_env'] = 'prod'
self.assertEqual( self.assertEqual(
account.get_password(),
account._get_password(),
'abc', 'Should work with prod') 'abc', 'Should work with prod')
def test_multienv_force(self): def test_multienv_force(self):
@ -175,12 +175,12 @@ class TestKeychain(TransactionCase):
with self.assertRaises(Warning): with self.assertRaises(Warning):
self.assertEqual( self.assertEqual(
account.get_password(),
account._get_password(),
'abc', 'Should not work with dev') 'abc', 'Should not work with dev')
config['running_env'] = 'prod' config['running_env'] = 'prod'
self.assertEqual( self.assertEqual(
account.get_password(),
account._get_password(),
'abc', 'Should work with prod') 'abc', 'Should work with prod')
def test_wrong_json(self): def test_wrong_json(self):

81
keychain/tests/test_keychain_backend.py

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion Mourad EL HADJ MIMOUNE
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tests.common import TransactionCase
from openerp.tools.config import config
import logging
_logger = logging.getLogger(__name__)
try:
from cryptography.fernet import Fernet
except ImportError as err:
_logger.debug(err)
class TestKeychain(TransactionCase):
def setUp(self):
super(TestKeychain, self).setUp()
self.keychain = self.env['keychain.account']
self.keychain_backend = self.env['keychain.backend']
def _init_data(self):
return {
"c": True,
"a": "b",
"d": "",
}
def _validate_data(self, data):
return 'c' in data
keychain_clss = self.keychain.__class__
keychain_clss._test_backend_init_data = _init_data
keychain_clss._test_backend_validate_data = _validate_data
keychain_backend_clss = self.keychain_backend.__class__
keychain_backend_clss._backend_name = 'test_backend'
self.keychain._fields['namespace'].selection.append(
('test_backend', 'test backend')
)
def test_keychain_bakend(self):
"""It should work with valid data."""
config['keychain_key_dev'] = Fernet.generate_key()
config['running_env'] = 'dev'
vals = {
'name': 'backend_test',
'password': 'test',
'data': '{"a": "o", "c": "b"}'
}
# we use new because keychain.backend is an abstract model
backend = self.keychain_backend.new(vals)
backend._inverse_keychain()
account = backend._get_existing_keychain()
self.assertEqual(
backend.data, '{"a": "o", "c": "b"}',
'Account data is not correct')
backend._inverse_password()
self.assertTrue(account, 'Account was not created')
self.assertEqual(
account.clear_password, u'test',
'Account clear password is not correct')
self.assertEqual(backend.password, u'test')
backend._compute_password()
self.assertEqual(
backend.password, u'******', 'Backend password was not computed')
self.assertEqual(
account.name, u'backend_test dev', 'Account name is not correct')
self.assertEqual(
account.namespace, u'test_backend',
'Account namespace is not correct')
self.assertEqual(
account.environment, u'dev', 'Account environment is not correct')
self.assertEqual(
account.technical_name, '%s,%s' % (backend._name, backend.id),
'Account technical_name is not correct')

4
keychain/views/keychain_view.xml

@ -5,7 +5,7 @@
<field name="model">keychain.account</field> <field name="model">keychain.account</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Accounts">
<tree>
<field name="namespace"/> <field name="namespace"/>
<field name="name"/> <field name="name"/>
<field name="technical_name" /> <field name="technical_name" />
@ -19,7 +19,7 @@
<record model="ir.ui.view" id="keychain_account_form"> <record model="ir.ui.view" id="keychain_account_form">
<field name="model">keychain.account</field> <field name="model">keychain.account</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Accounts form">
<form>
<group> <group>
<field name="namespace"/> <field name="namespace"/>
<field name="name" /> <field name="name" />

Loading…
Cancel
Save