diff --git a/addons/auth_crypt/__init__.py b/addons/auth_crypt/__init__.py index 11f3889e8a8..48f417bbd93 100644 --- a/addons/auth_crypt/__init__.py +++ b/addons/auth_crypt/__init__.py @@ -1,3 +1,3 @@ +# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - -import auth_crypt +import models diff --git a/addons/auth_crypt/__openerp__.py b/addons/auth_crypt/__openerp__.py index 3f28640ba19..9236fc18f1d 100644 --- a/addons/auth_crypt/__openerp__.py +++ b/addons/auth_crypt/__openerp__.py @@ -1,10 +1,11 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + { 'name': 'Password Encryption', 'version': '2.0', 'author': ['Odoo SA', 'FS3'], - 'maintainer': 'OpenERP SA', + 'maintainer': 'Odoo SA', 'category': 'Tools', 'description': """ Encrypted passwords @@ -34,7 +35,5 @@ necessarily have to be enabled). """, 'depends': ['base'], - 'data': [], 'auto_install': True, - 'installable': True, } diff --git a/addons/auth_crypt/auth_crypt.py b/addons/auth_crypt/auth_crypt.py deleted file mode 100644 index 7c9d0c5fa2c..00000000000 --- a/addons/auth_crypt/auth_crypt.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from passlib.context import CryptContext - -import openerp -from openerp.osv import fields, osv - -from openerp.addons.base.res import res_users -res_users.USER_PRIVATE_FIELDS.append('password_crypt') - -_logger = logging.getLogger(__name__) - -default_crypt_context = CryptContext( - # kdf which can be verified by the context. The default encryption kdf is - # the first of the list - ['pbkdf2_sha512', 'md5_crypt'], - # deprecated algorithms are still verified as usual, but ``needs_update`` - # will indicate that the stored hash should be replaced by a more recent - # algorithm. Passlib 1.6 supports an `auto` value which deprecates any - # algorithm but the default, but Ubuntu LTS only provides 1.5 so far. - deprecated=['md5_crypt'], -) - -class res_users(osv.osv): - _inherit = "res.users" - - def init(self, cr): - _logger.info("Hashing passwords, may be slow for databases with many users...") - cr.execute("SELECT id, password FROM res_users" - " WHERE password IS NOT NULL" - " AND password != ''") - for uid, pwd in cr.fetchall(): - self._set_password(cr, openerp.SUPERUSER_ID, uid, pwd) - - def set_pw(self, cr, uid, id, name, value, args, context): - if value: - self._set_password(cr, uid, id, value, context=context) - self.invalidate_cache(cr, uid, context=context) - - def get_pw( self, cr, uid, ids, name, args, context ): - cr.execute('select id, password from res_users where id in %s', (tuple(map(int, ids)),)) - return dict(cr.fetchall()) - - _columns = { - 'password': fields.function(get_pw, fnct_inv=set_pw, type='char', string='Password', invisible=True, store=True), - 'password_crypt': fields.char(string='Encrypted Password', invisible=True, copy=False), - } - - def check_credentials(self, cr, uid, password): - # convert to base_crypt if needed - cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (uid,)) - encrypted = None - if cr.rowcount: - stored, encrypted = cr.fetchone() - if stored and not encrypted: - self._set_password(cr, uid, uid, stored) - self.invalidate_cache(cr, uid) - try: - return super(res_users, self).check_credentials(cr, uid, password) - except openerp.exceptions.AccessDenied: - if encrypted: - valid_pass, replacement = self._crypt_context(cr, uid, uid)\ - .verify_and_update(password, encrypted) - if replacement is not None: - self._set_encrypted_password(cr, uid, uid, replacement) - if valid_pass: - return - - raise - - def _set_password(self, cr, uid, id, password, context=None): - """ Encrypts then stores the provided plaintext password for the user - ``id`` - """ - encrypted = self._crypt_context(cr, uid, id, context=context).encrypt(password) - self._set_encrypted_password(cr, uid, id, encrypted, context=context) - - def _set_encrypted_password(self, cr, uid, id, encrypted, context=None): - """ Store the provided encrypted password to the database, and clears - any plaintext password - - :param uid: id of the current user - :param id: id of the user on which the password should be set - """ - cr.execute( - "UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", - (encrypted, id)) - - def _crypt_context(self, cr, uid, id, context=None): - """ Passlib CryptContext instance used to encrypt and verify - passwords. Can be overridden if technical, legal or political matters - require different kdfs than the provided default. - - Requires a CryptContext as deprecation and upgrade notices are used - internally - """ - return default_crypt_context diff --git a/addons/auth_crypt/models/__init__.py b/addons/auth_crypt/models/__init__.py new file mode 100644 index 00000000000..ab149929a18 --- /dev/null +++ b/addons/auth_crypt/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import res_users diff --git a/addons/auth_crypt/models/res_users.py b/addons/auth_crypt/models/res_users.py new file mode 100644 index 00000000000..708998acbde --- /dev/null +++ b/addons/auth_crypt/models/res_users.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +import logging +from passlib.context import CryptContext + +import odoo +from odoo import api, fields, models + +from odoo.addons.base.res import res_users +res_users.USER_PRIVATE_FIELDS.append('password_crypt') + +_logger = logging.getLogger(__name__) + +default_crypt_context = CryptContext( + # kdf which can be verified by the context. The default encryption kdf is + # the first of the list + ['pbkdf2_sha512', 'md5_crypt'], + # deprecated algorithms are still verified as usual, but ``needs_update`` + # will indicate that the stored hash should be replaced by a more recent + # algorithm. Passlib 1.6 supports an `auto` value which deprecates any + # algorithm but the default, but Ubuntu LTS only provides 1.5 so far. + deprecated=['md5_crypt'], +) + + +class ResUsers(models.Model): + _inherit = "res.users" + + def init(self): + _logger.info("Hashing passwords, may be slow for databases with many users...") + self.env.cr.execute("SELECT id, password FROM res_users" + " WHERE password IS NOT NULL" + " AND password != ''") + for uid, pwd in self.env.cr.fetchall(): + self.sudo().browse(uid)._set_password(pwd) + + password = fields.Char(compute='_compute_password', inverse='_inverse_password', invisible=True, store=True) + password_crypt = fields.Char(string='Encrypted Password', invisible=True, copy=False) + + def _compute_password(self): + self.env.cr.execute('SELECT id, password FROM res_users WHERE id IN %s', [tuple(self.ids)]) + password_dict = dict(self.env.cr.fetchall()) + for user in self: + user.password = password_dict[user.id] + + def _inverse_password(self): + for user in self: + user._set_password(user.password) + self.invalidate_cache() + + @api.model + def check_credentials(self, password): + # convert to base_crypt if needed + self.env.cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (self.env.uid,)) + encrypted = None + user = self.env.user + if self.env.cr.rowcount: + stored, encrypted = self.env.cr.fetchone() + if stored and not encrypted: + user._set_password(stored) + self.invalidate_cache() + try: + return super(ResUsers, self).check_credentials(password) + except odoo.exceptions.AccessDenied: + if encrypted: + valid_pass, replacement = user._crypt_context()\ + .verify_and_update(password, encrypted) + if replacement is not None: + user._set_encrypted_password(replacement) + if valid_pass: + return + raise + + def _set_password(self, password): + self.ensure_one() + """ Encrypts then stores the provided plaintext password for the user + ``self`` + """ + encrypted = self._crypt_context().encrypt(password) + self._set_encrypted_password(encrypted) + + def _set_encrypted_password(self, encrypted): + """ Store the provided encrypted password to the database, and clears + any plaintext password + """ + self.env.cr.execute( + "UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", + (encrypted, self.id)) + + def _crypt_context(self): + """ Passlib CryptContext instance used to encrypt and verify + passwords. Can be overridden if technical, legal or political matters + require different kdfs than the provided default. + + Requires a CryptContext as deprecation and upgrade notices are used + internally + """ + return default_crypt_context